Merge branch 'master' into instruction
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { onChangeWorkspace } from '../../actions/workspaceActions';
|
||||
import { onChangeWorkspace, clearStats } from '../../actions/workspaceActions';
|
||||
import * as De from './msg/de';
|
||||
import BlocklyComponent from './';
|
||||
import * as Blockly from 'blockly/core';
|
||||
@@ -22,23 +22,25 @@ class BlocklyWindow extends Component {
|
||||
componentDidMount() {
|
||||
const workspace = Blockly.getMainWorkspace();
|
||||
this.props.onChangeWorkspace({});
|
||||
this.props.clearStats();
|
||||
workspace.addChangeListener((event) => {
|
||||
this.props.onChangeWorkspace(event);
|
||||
Blockly.Events.disableOrphans(event);
|
||||
});
|
||||
Blockly.svgResize(workspace);
|
||||
}
|
||||
|
||||
componentDidUpdate(props) {
|
||||
const workspace = Blockly.getMainWorkspace();
|
||||
if(props.initialXml !== this.props.initialXml){
|
||||
// guarantees that the current xml-code (this.props.initialXml) is rendered
|
||||
const workspace = Blockly.getMainWorkspace();
|
||||
workspace.clear();
|
||||
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(this.props.initialXml), workspace);
|
||||
}
|
||||
Blockly.svgResize(workspace);
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log(this.props.initialXml);
|
||||
return (
|
||||
<BlocklyComponent ref={this.simpleWorkspace}
|
||||
style={this.props.blocklyCSS}
|
||||
@@ -76,8 +78,9 @@ class BlocklyWindow extends Component {
|
||||
}
|
||||
|
||||
BlocklyWindow.propTypes = {
|
||||
onChangeWorkspace: PropTypes.func.isRequired
|
||||
onChangeWorkspace: PropTypes.func.isRequired,
|
||||
clearStats: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
export default connect(null, { onChangeWorkspace })(BlocklyWindow);
|
||||
export default connect(null, { onChangeWorkspace, clearStats })(BlocklyWindow);
|
||||
|
||||
@@ -7,6 +7,7 @@ import './sensebox-osem';
|
||||
import './sensebox-web';
|
||||
import './sensebox-display';
|
||||
import './sensebox-lora';
|
||||
import './sensebox-led';
|
||||
import './io';
|
||||
import './math';
|
||||
import './map';
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import * as Blockly from 'blockly/core';
|
||||
import { getColour } from '../helpers/colour';
|
||||
import * as Types from '../helpers/types'
|
||||
import { FieldSlider } from '@blockly/field-slider';
|
||||
import { Field } from '..';
|
||||
|
||||
|
||||
Blockly.Blocks['sensebox_display_beginDisplay'] = {
|
||||
@@ -35,12 +37,15 @@ Blockly.Blocks['sensebox_display_printDisplay'] = {
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.senseBox_display_color)
|
||||
.appendField(new Blockly.FieldDropdown([[Blockly.Msg.senseBox_display_white, "WHITE,BLACK"], [Blockly.Msg.senseBox_display_black, "BLACK,WHITE"]]), "COLOR");
|
||||
this.appendValueInput("SIZE", 'Number')
|
||||
.appendField(Blockly.Msg.senseBox_display_setSize);
|
||||
this.appendValueInput("X", 'Number')
|
||||
.appendField(Blockly.Msg.senseBox_display_printDisplay_x);
|
||||
this.appendValueInput("Y", 'Number')
|
||||
.appendField(Blockly.Msg.senseBox_display_printDisplay_y);
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.senseBox_display_setSize)
|
||||
.appendField(new FieldSlider(1, 1, 4), "SIZE");
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.senseBox_display_printDisplay_x)
|
||||
.appendField(new FieldSlider(0, 0, 64), "X");
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.senseBox_display_printDisplay_y)
|
||||
.appendField(new FieldSlider(0, 0, 128), "Y");
|
||||
this.appendValueInput('printDisplay')
|
||||
.appendField(Blockly.Msg.senseBox_display_printDisplay_value)
|
||||
.setCheck(null);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as Blockly from 'blockly';
|
||||
import { FieldSlider } from '@blockly/field-slider';
|
||||
import { getColour } from '../helpers/colour'
|
||||
import { selectedBoard } from '../helpers/board'
|
||||
|
||||
|
||||
Blockly.Blocks['sensebox_rgb_led'] = {
|
||||
init: function () {
|
||||
this.setColour(getColour().sensebox);
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.senseBox_rgb_led)
|
||||
.appendField("Pin:")
|
||||
.appendField(new Blockly.FieldDropdown(selectedBoard().digitalPins), "PIN")
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.COLOUR_RGB_RED)//Blockly.Msg.senseBox_basic_red
|
||||
.appendField(new FieldSlider(255, 0, 255), "RED");
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.COLOUR_RGB_GREEN)//Blockly.Msg.senseBox_basic_green
|
||||
.appendField(new FieldSlider(255, 0, 255), "GREEN");
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.COLOUR_RGB_BLUE)//Blockly.Msg.senseBox_basic_green
|
||||
.appendField(new FieldSlider(255, 0, 255), "BLUE");
|
||||
this.setPreviousStatement(true, null);
|
||||
this.setNextStatement(true, null);
|
||||
this.setTooltip(Blockly.Msg.senseBox_rgb_led_tip);
|
||||
this.setHelpUrl('https://sensebox.de/books');
|
||||
}
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import './sensebox-osem';
|
||||
import './sensebox-web';
|
||||
import './sensebox-display';
|
||||
import './sensebox-lora';
|
||||
import './sensebox-led';
|
||||
import './logic';
|
||||
import './math';
|
||||
import './map';
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import * as Blockly from 'blockly/core';
|
||||
import { Block } from 'blockly';
|
||||
|
||||
Blockly.Arduino.sensebox_rgb_led = function () {
|
||||
var dropdown_pin = this.getFieldValue('PIN');
|
||||
var red = this.getFieldValue('RED') || '0'
|
||||
var green = this.getFieldValue('GREEN') || '0'
|
||||
var blue = this.getFieldValue('BLUE') || '0'
|
||||
Blockly.Arduino.libraries_['define_rgb_led' + dropdown_pin] = '#include <Adafruit_NeoPixel.h>\n Adafruit_NeoPixel rgb_led_' + dropdown_pin + ' = Adafruit_NeoPixel(1,' + dropdown_pin + ',NEO_RGB + NEO_KHZ800);\n';
|
||||
Blockly.Arduino.setupCode_['setup_rgb_led' + dropdown_pin] = 'rgb_led_' + dropdown_pin + '.begin();';
|
||||
|
||||
var code = 'rgb_led_' + dropdown_pin + '.setPixelColor(0,rgb_led_' + dropdown_pin + '.Color(' + red + ',' + green + ',' + blue + '));\n';
|
||||
code += 'rgb_led_' + dropdown_pin + '.show();';
|
||||
return code;
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Block, Value, Field, Shadow, Category } from '../';
|
||||
import { getColour } from '../helpers/colour'
|
||||
import '@blockly/block-plus-minus';
|
||||
|
||||
|
||||
class Toolbox extends React.Component {
|
||||
@@ -23,6 +24,9 @@ class Toolbox extends React.Component {
|
||||
<Block type="sensebox_wifi" />
|
||||
<Block type="sensebox_startap" />
|
||||
</Category>
|
||||
<Category name="LED" colour={getColour().sensebox}>
|
||||
<Block type="sensebox_rgb_led" />
|
||||
</Category>
|
||||
<Category name="Display" colour={getColour().sensebox}>
|
||||
<Block type="sensebox_display_beginDisplay" />
|
||||
<Block type="sensebox_display_show" />
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import BlocklyWindow from '../Blockly/BlocklyWindow';
|
||||
import SolutionCheck from './SolutionCheck';
|
||||
import CodeViewer from '../CodeViewer';
|
||||
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
class Assessment extends Component {
|
||||
|
||||
render() {
|
||||
var tutorialId = this.props.currentTutorialId;
|
||||
var currentTask = this.props.step;
|
||||
var status = this.props.status.filter(status => status.id === tutorialId)[0];
|
||||
var taskIndex = status.tasks.findIndex(task => task.id === currentTask.id);
|
||||
var statusTask = status.tasks[taskIndex];
|
||||
|
||||
return (
|
||||
<div style={{width: '100%'}}>
|
||||
<Typography variant='h4' style={{marginBottom: '5px'}}>{currentTask.headline}</Typography>
|
||||
<Grid container spacing={2} style={{marginBottom: '5px'}}>
|
||||
<Grid item xs={12} md={6} lg={8} style={{ position: 'relative' }}>
|
||||
<SolutionCheck />
|
||||
<BlocklyWindow initialXml={statusTask.xml ? statusTask.xml : null}/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Card style={{height: 'calc(50% - 30px)', padding: '10px', marginBottom: '10px'}}>
|
||||
<Typography variant='h5'>Arbeitsauftrag</Typography>
|
||||
<Typography>{currentTask.text1}</Typography>
|
||||
</Card>
|
||||
<div style={{height: '50%'}}>
|
||||
<CodeViewer />
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
Assessment.propTypes = {
|
||||
currentTutorialId: PropTypes.number,
|
||||
status: PropTypes.array.isRequired,
|
||||
change: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
change: state.tutorial.change,
|
||||
status: state.tutorial.status,
|
||||
currentTutorialId: state.tutorial.currentId
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(Assessment);
|
||||
@@ -5,8 +5,6 @@ import { connect } from 'react-redux';
|
||||
import Hardware from './Hardware';
|
||||
import BlocklyWindow from '../Blockly/BlocklyWindow';
|
||||
|
||||
import { tutorials } from './tutorials';
|
||||
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { tutorialCheck } from '../../actions/tutorialActions';
|
||||
import { tutorialCheck, tutorialStep } from '../../actions/tutorialActions';
|
||||
|
||||
import * as Blockly from 'blockly/core';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import Compile from '../Compile';
|
||||
|
||||
import { tutorials } from './tutorials';
|
||||
import tutorials from './tutorials.json';
|
||||
import { checkXml } from './compareXml';
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
@@ -48,62 +49,79 @@ class SolutionCheck extends Component {
|
||||
}
|
||||
|
||||
check = () => {
|
||||
const workspace = Blockly.getMainWorkspace();
|
||||
var msg = tutorials[this.props.currentTutorialId].test(workspace);
|
||||
this.props.tutorialCheck(msg.type);
|
||||
const tutorial = tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0];
|
||||
const step = tutorial.steps[this.props.activeStep];
|
||||
var msg = checkXml(step.xml, this.props.xml);
|
||||
this.props.tutorialCheck(msg.type, step);
|
||||
this.setState({ msg, open: true });
|
||||
}
|
||||
|
||||
render() {
|
||||
const steps = tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0].steps;
|
||||
return (
|
||||
tutorials[this.props.currentTutorialId].test ?
|
||||
<div>
|
||||
<Tooltip title='Lösung kontrollieren'>
|
||||
<IconButton
|
||||
className={this.props.classes.compile}
|
||||
style={{width: '40px', height: '40px', position: 'absolute', top: 8, right: 8, zIndex: 21 }}
|
||||
onClick={() => this.check()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlay} size="xs"/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog fullWidth maxWidth={'sm'} onClose={this.toggleDialog} open={this.state.open} style={{zIndex: 9999999}}>
|
||||
<DialogTitle>{this.state.msg.type === 'error' ? 'Fehler' : 'Erfolg'}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
{this.state.msg.text}
|
||||
{this.state.msg.type === 'success' ?
|
||||
<div>
|
||||
<Tooltip title='Lösung kontrollieren'>
|
||||
<IconButton
|
||||
className={this.props.classes.compile}
|
||||
style={{width: '40px', height: '40px', position: 'absolute', top: 8, right: 8, zIndex: 21 }}
|
||||
onClick={() => this.check()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlay} size="xs"/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog fullWidth maxWidth={'sm'} onClose={this.toggleDialog} open={this.state.open} style={{zIndex: 9999999}}>
|
||||
<DialogTitle>{this.state.msg.type === 'error' ? 'Fehler' : 'Erfolg'}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
{this.state.msg.text}
|
||||
{this.state.msg.type === 'success' ?
|
||||
<div style={{marginTop: '20px', display: 'flex'}}>
|
||||
<Compile />
|
||||
<Button
|
||||
style={{marginLeft: '10px'}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {this.toggleDialog(); this.props.history.push(`/tutorial/${this.props.currentTutorialId+2}`)}}
|
||||
>
|
||||
nächstes Tutorial
|
||||
</Button>
|
||||
</div>
|
||||
: null}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.toggleDialog} color="primary">
|
||||
Schließen
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
: null
|
||||
{this.props.activeStep === steps.length-1 ?
|
||||
<Button
|
||||
style={{marginLeft: '10px'}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {this.toggleDialog(); this.props.history.push(`/tutorial/`)}}
|
||||
>
|
||||
Tutorials-Übersicht
|
||||
</Button>
|
||||
:
|
||||
<Button
|
||||
style={{marginLeft: '10px'}}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {this.toggleDialog(); this.props.tutorialStep(this.props.activeStep + 1)}}
|
||||
>
|
||||
nächster Schritt
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
: null}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={this.toggleDialog} color="primary">
|
||||
Schließen
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
SolutionCheck.propTypes = {
|
||||
tutorialCheck: PropTypes.func.isRequired,
|
||||
currentTutorialId: PropTypes.number
|
||||
tutorialStep: PropTypes.func.isRequired,
|
||||
currentTutorialId: PropTypes.number,
|
||||
activeStep: PropTypes.number.isRequired,
|
||||
xml: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
currentTutorialId: state.tutorial.currentId
|
||||
currentTutorialId: state.tutorial.currentId,
|
||||
activeStep: state.tutorial.activeStep,
|
||||
xml: state.workspace.code.xml
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { tutorialCheck })(withStyles(styles, {withTheme: true})(SolutionCheck));
|
||||
export default connect(mapStateToProps, { tutorialCheck, tutorialStep })(withStyles(styles, {withTheme: true})(withRouter(SolutionCheck)));
|
||||
|
||||
@@ -6,7 +6,7 @@ import { withRouter } from 'react-router-dom';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { tutorials } from './tutorials';
|
||||
import tutorials from './tutorials.json';
|
||||
|
||||
import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
@@ -21,6 +21,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
const styles = (theme) => ({
|
||||
stepper: {
|
||||
width: 'calc(100% - 40px)',
|
||||
height: '40px',
|
||||
borderRadius: '25px',
|
||||
padding: '0 20px',
|
||||
margin: '20px 0',
|
||||
@@ -51,30 +52,43 @@ class StepperHorizontal extends Component {
|
||||
|
||||
render() {
|
||||
var tutorialId = this.props.currentTutorialId;
|
||||
var tutorialStatus = this.props.status[tutorialId].status === 'success' ? 'Success' :
|
||||
this.props.status[tutorialId].status === 'error' ? 'Error' : 'Other';
|
||||
var status = this.props.status.filter(status => status.id === tutorialId)[0];
|
||||
var tasks = status.tasks;
|
||||
var error = tasks.filter(task => task.type === 'error').length > 0;
|
||||
var success = tasks.filter(task => task.type === 'success').length / tasks.length;
|
||||
var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other';
|
||||
return (
|
||||
<div className={clsx(this.props.classes.stepper, this.props.classes['stepper'+tutorialStatus])}>
|
||||
<Button
|
||||
disabled={tutorialId === 0}
|
||||
onClick={() => {this.props.history.push(`/tutorial/${tutorialId}`)}}
|
||||
>
|
||||
{'<'}
|
||||
</Button>
|
||||
<Stepper activeStep={tutorialId+1} orientation="horizontal"
|
||||
style={{padding: 0}} classes={{root: this.props.classes.color}}>
|
||||
<Step expanded completed={false}>
|
||||
<StepLabel icon={tutorialStatus !== 'Other' ? <div className={clsx(tutorialStatus === 'Error' ? this.props.classes.iconDivError: this.props.classes.iconDivSuccess)}><FontAwesomeIcon className={this.props.classes.icon} icon={tutorialStatus === 'Success' ? faCheck : faTimes}/></div> : ''}>
|
||||
<h1 style={{margin: 0}}>{tutorials[tutorialId].title}</h1>
|
||||
</StepLabel>
|
||||
</Step>
|
||||
</Stepper>
|
||||
<Button
|
||||
disabled={tutorialId+2 > tutorials.length}
|
||||
onClick={() => {this.props.history.push(`/tutorial/${tutorialId+2}`)}}
|
||||
>
|
||||
{'>'}
|
||||
</Button>
|
||||
<div style={{position: 'relative'}}>
|
||||
{error || success > 0 ?
|
||||
<div style={{zIndex: -1, width: error ? 'calc(100% - 40px)' : `calc(${success*100}% - 40px)`, borderRadius: success === 1 || error ? '25px' : '25px 0 0 25px', position: 'absolute', margin: 0, left: 0}} className={clsx(this.props.classes.stepper, error ? this.props.classes.stepperError : this.props.classes.stepperSuccess)}>
|
||||
</div>
|
||||
: null}
|
||||
{success < 1 && !error ?
|
||||
<div style={{zIndex: -2, width: `calc(${(1-success)*100}% - 40px)`, borderRadius: success === 0 ? '25px' : '0px 25px 25px 0', position: 'absolute', margin: 0, right: 0}} className={clsx(this.props.classes.stepper, this.props.classes.stepperOther)}>
|
||||
</div>
|
||||
: null}
|
||||
<div className={this.props.classes.stepper}>
|
||||
<Button
|
||||
disabled={tutorialId === 1}
|
||||
onClick={() => {this.props.history.push(`/tutorial/${tutorialId-1}`)}}
|
||||
>
|
||||
{'<'}
|
||||
</Button>
|
||||
<Stepper activeStep={tutorialId} orientation="horizontal"
|
||||
style={{padding: 0}} classes={{root: this.props.classes.color}}>
|
||||
<Step expanded completed={false}>
|
||||
<StepLabel icon={tutorialStatus !== 'Other' ? <div className={tutorialStatus === 'Success' && success === 1 ? this.props.classes.iconDivSuccess : this.props.classes.iconDivError}><FontAwesomeIcon className={this.props.classes.icon} icon={tutorialStatus === 'Success' ? faCheck : faTimes}/></div> : ''}>
|
||||
<h1 style={{margin: 0}}>{tutorials.filter(tutorial => tutorial.id === tutorialId)[0].title}</h1>
|
||||
</StepLabel>
|
||||
</Step>
|
||||
</Stepper>
|
||||
<Button
|
||||
disabled={tutorialId+1 > tutorials.length}
|
||||
onClick={() => {this.props.history.push(`/tutorial/${tutorialId+1}`)}}
|
||||
>
|
||||
{'>'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { tutorialStep } from '../../actions/tutorialActions';
|
||||
|
||||
import { withRouter, Link } from 'react-router-dom';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { tutorials } from './tutorials';
|
||||
|
||||
import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import withWidth, { isWidthUp } from '@material-ui/core/withWidth';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Stepper from '@material-ui/core/Stepper';
|
||||
import Step from '@material-ui/core/Step';
|
||||
import StepLabel from '@material-ui/core/StepLabel';
|
||||
@@ -26,30 +23,23 @@ const styles = (theme) => ({
|
||||
borderStyle: `solid`,
|
||||
// borderWidth: '2px',
|
||||
borderRadius: '50%',
|
||||
borderColor: theme.palette.secondary.main,
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
margin: '0 auto'
|
||||
},
|
||||
stepIconMedium: {
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
margin: '0 auto',
|
||||
},
|
||||
stepIconLarge: {
|
||||
width: '24px',
|
||||
height: '24px'
|
||||
},
|
||||
stepIconTransparent: {
|
||||
borderColor: `transparent`,
|
||||
cursor: 'default'
|
||||
},
|
||||
stepIconSuccess: {
|
||||
stepIconLargeSuccess: {
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
stepIconError: {
|
||||
stepIconLargeError: {
|
||||
borderColor: theme.palette.error.dark,
|
||||
},
|
||||
stepIconOther: {
|
||||
borderColor: theme.palette.secondary.main,
|
||||
stepIconActiveOther: {
|
||||
backgroundColor: theme.palette.secondary.main
|
||||
},
|
||||
stepIconActiveSuccess: {
|
||||
backgroundColor: fade(theme.palette.primary.main, 0.6)
|
||||
@@ -57,145 +47,64 @@ const styles = (theme) => ({
|
||||
stepIconActiveError: {
|
||||
backgroundColor: fade(theme.palette.error.dark, 0.6)
|
||||
},
|
||||
stepIconActiveOther: {
|
||||
backgroundColor: fade(theme.palette.secondary.main, 0.6)
|
||||
},
|
||||
progress: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
marginRight: '5px',
|
||||
width: '3px',
|
||||
},
|
||||
progressForeground: {
|
||||
backgroundColor: theme.palette.primary.main
|
||||
},
|
||||
progressBackground: {
|
||||
backgroundColor: fade(theme.palette.primary.main, 0.2),
|
||||
height: '100%',
|
||||
borderRadius: '2px'
|
||||
connector: {
|
||||
height: '10px',
|
||||
borderLeft: `2px solid black`,
|
||||
margin: 'auto'
|
||||
}
|
||||
});
|
||||
|
||||
class StepperVertical extends Component {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
tutorialArray: props.currentTutorialId === 0 ?
|
||||
tutorials.slice(props.currentTutorialId, props.currentTutorialId+5)
|
||||
: props.currentTutorialId === 1 ?
|
||||
tutorials.slice(props.currentTutorialId-1, props.currentTutorialId+4)
|
||||
: props.currentTutorialId === tutorials.length-1 ?
|
||||
tutorials.slice(props.currentTutorialId-4, props.currentTutorialId+5)
|
||||
: props.currentTutorialId === tutorials.length-2 ?
|
||||
tutorials.slice(props.currentTutorialId-3, props.currentTutorialId+4)
|
||||
: tutorials.slice(props.currentTutorialId-2, props.currentTutorialId+3),
|
||||
selectedVerticalTutorialId: props.currentTutorialId
|
||||
};
|
||||
componentDidMount(){
|
||||
this.props.tutorialStep(0);
|
||||
}
|
||||
|
||||
componentDidUpdate(props){
|
||||
if(props.currentTutorialId !== this.props.currentTutorialId){
|
||||
this.setState({
|
||||
tutorialArray: this.props.currentTutorialId === 0 ?
|
||||
tutorials.slice(this.props.currentTutorialId, this.props.currentTutorialId+5)
|
||||
: this.props.currentTutorialId === 1 ?
|
||||
tutorials.slice(this.props.currentTutorialId-1, this.props.currentTutorialId+4)
|
||||
: this.props.currentTutorialId === tutorials.length-1 ?
|
||||
tutorials.slice(this.props.currentTutorialId-4, this.props.currentTutorialId+5)
|
||||
: this.props.currentTutorialId === tutorials.length-2 ?
|
||||
tutorials.slice(this.props.currentTutorialId-3, this.props.currentTutorialId+4)
|
||||
: tutorials.slice(this.props.currentTutorialId-2, this.props.currentTutorialId+3),
|
||||
selectedVerticalTutorialId: this.props.currentTutorialId
|
||||
});
|
||||
if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){
|
||||
this.props.tutorialStep(0);
|
||||
}
|
||||
}
|
||||
|
||||
verticalStepper = (step) => {
|
||||
var newTutorialId = this.state.selectedVerticalTutorialId + step;
|
||||
var tutorialArray = newTutorialId === 0 ?
|
||||
tutorials.slice(newTutorialId, newTutorialId+5)
|
||||
: newTutorialId === 1 ?
|
||||
tutorials.slice(newTutorialId-1, newTutorialId+4)
|
||||
: newTutorialId === tutorials.length-1 ?
|
||||
tutorials.slice(newTutorialId-4, newTutorialId+5)
|
||||
: newTutorialId === tutorials.length-2 ?
|
||||
tutorials.slice(newTutorialId-3, newTutorialId+4)
|
||||
: tutorials.slice(newTutorialId-2, newTutorialId+3);
|
||||
this.setState({ tutorialArray: tutorialArray, selectedVerticalTutorialId: newTutorialId });
|
||||
}
|
||||
|
||||
render() {
|
||||
var tutorialId = this.props.currentTutorialId;
|
||||
var selectedVerticalTutorialId = this.state.selectedVerticalTutorialId;
|
||||
var steps = this.props.steps;
|
||||
var activeStep = this.props.activeStep;
|
||||
var tutorialStatus = this.props.status.filter(status => status.id === this.props.currentTutorialId)[0];
|
||||
return (
|
||||
isWidthUp('sm', this.props.width) ?
|
||||
<div style={{marginRight: '10px'}}>
|
||||
<Button
|
||||
style={{minWidth: '30px', margin: 'auto', minHeight: '25px', padding: '0', writingMode: 'vertical-rl'}}
|
||||
disabled={selectedVerticalTutorialId === 0}
|
||||
onClick={() => {this.verticalStepper(-1)}}
|
||||
>
|
||||
{'<'}
|
||||
</Button>
|
||||
<div style={{display: 'flex', height: 'max-content', width: 'max-content'}}>
|
||||
<div style={{position: 'relative'}}>
|
||||
<div
|
||||
className={clsx(this.props.classes.progress, this.props.classes.progressForeground)}
|
||||
style={{ zIndex: 1, borderRadius: `${selectedVerticalTutorialId/(tutorials.length-1) === 1 ? '2px' : '2px 2px 0 0'}`, height: `${((selectedVerticalTutorialId+1)/tutorials.length)*100}%`}}>
|
||||
</div>
|
||||
<div className={clsx(this.props.classes.progress, this.props.classes.progressBackground)}>
|
||||
</div>
|
||||
</div>
|
||||
<Stepper
|
||||
activeStep={tutorialId+1}
|
||||
orientation="vertical"
|
||||
connector={<div style={{height: '10px'}}></div>}
|
||||
classes={{root: this.props.classes.verticalStepper}}
|
||||
>
|
||||
{this.state.tutorialArray.map((tutorial, i) => {
|
||||
var index = this.state.tutorialArray.indexOf(tutorials[selectedVerticalTutorialId]);
|
||||
var verticalTutorialId = i === index ? selectedVerticalTutorialId+1 : selectedVerticalTutorialId+1 - index + i;
|
||||
var tutorialStatus = this.props.status[verticalTutorialId-1].status === 'success' ? 'Success' :
|
||||
this.props.status[verticalTutorialId-1].status === 'error' ? 'Error' : 'Other';
|
||||
return (
|
||||
<Step key={i}>
|
||||
<Tooltip title={Object.keys(tutorial).length > 0 ? tutorial.title : ''} placement='right' arrow >
|
||||
<Link to={`/tutorial/${verticalTutorialId}`}>
|
||||
<StepLabel
|
||||
StepIconComponent={'div'}
|
||||
classes={{
|
||||
root: tutorial === tutorials[selectedVerticalTutorialId] ?
|
||||
tutorial === tutorials[tutorialId] ?
|
||||
clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIcon'+tutorialStatus], this.props.classes['stepIconActive'+tutorialStatus])
|
||||
: clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIcon'+tutorialStatus])
|
||||
: tutorial === tutorials[selectedVerticalTutorialId-1] || tutorial === tutorials[selectedVerticalTutorialId+1] ||
|
||||
tutorial === tutorials[verticalTutorialId-2] ?
|
||||
tutorial === tutorials[tutorialId] ?
|
||||
clsx(this.props.classes.stepIcon, this.props.classes.stepIconMedium, this.props.classes['stepIcon'+tutorialStatus], this.props.classes['stepIconActive'+tutorialStatus])
|
||||
: clsx(this.props.classes.stepIcon, this.props.classes.stepIconMedium, this.props.classes['stepIcon'+tutorialStatus])
|
||||
: tutorial === tutorials[tutorialId] ?
|
||||
clsx(this.props.classes.stepIcon, this.props.classes['stepIcon'+tutorialStatus], this.props.classes['stepIconActive'+tutorialStatus])
|
||||
: clsx(this.props.classes.stepIcon, this.props.classes['stepIcon'+tutorialStatus])
|
||||
}}
|
||||
>
|
||||
</StepLabel>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
</Step>
|
||||
)})}
|
||||
</Stepper>
|
||||
</div>
|
||||
<Button
|
||||
style={{minWidth: '30px', minHeight: '25px', padding: '0', writingMode: 'vertical-rl'}}
|
||||
disabled={selectedVerticalTutorialId === tutorials.length-1}
|
||||
onClick={() => {this.verticalStepper(1)}}
|
||||
>
|
||||
{'>'}
|
||||
</Button>
|
||||
</div>
|
||||
: null
|
||||
<div style={{marginRight: '10px'}}>
|
||||
<Stepper
|
||||
activeStep={activeStep}
|
||||
orientation="vertical"
|
||||
connector={<div className={this.props.classes.connector}></div>}
|
||||
classes={{root: this.props.classes.verticalStepper}}
|
||||
>
|
||||
{steps.map((step, i) => {
|
||||
var tasksIndex = tutorialStatus.tasks.findIndex(task => task.id === step.id);
|
||||
var taskType = tasksIndex > -1 ? tutorialStatus.tasks[tasksIndex].type : null;
|
||||
var taskStatus = taskType === 'success' ? 'Success' : taskType === 'error' ? 'Error' : 'Other';
|
||||
return (
|
||||
<Step key={i}>
|
||||
<Tooltip title={step.headline} placement='right' arrow >
|
||||
<div style={i === activeStep ? {padding: '5px 0'} : {padding: '5px 0', cursor: 'pointer'}} onClick={i === activeStep ? null : () => {this.props.tutorialStep(i)}}>
|
||||
<StepLabel
|
||||
StepIconComponent={'div'}
|
||||
classes={{
|
||||
root: step.type === 'task' ?
|
||||
i === activeStep ?
|
||||
clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIconLarge'+taskStatus], this.props.classes['stepIconActive'+taskStatus])
|
||||
: clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIconLarge'+taskStatus])
|
||||
: i === activeStep ?
|
||||
clsx(this.props.classes.stepIcon, this.props.classes.stepIconActiveOther)
|
||||
: clsx(this.props.classes.stepIcon)
|
||||
}}
|
||||
>
|
||||
</StepLabel>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Step>
|
||||
)})}
|
||||
</Stepper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -204,13 +113,16 @@ class StepperVertical extends Component {
|
||||
StepperVertical.propTypes = {
|
||||
status: PropTypes.array.isRequired,
|
||||
change: PropTypes.number.isRequired,
|
||||
currentTutorialId: PropTypes.number.isRequired
|
||||
currentTutorialId: PropTypes.number.isRequired,
|
||||
activeStep: PropTypes.number.isRequired,
|
||||
tutorialStep: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
change: state.tutorial.change,
|
||||
status: state.tutorial.status,
|
||||
currentTutorialId: state.tutorial.currentId
|
||||
currentTutorialId: state.tutorial.currentId,
|
||||
activeStep: state.tutorial.activeStep
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(withRouter(withStyles(styles, {withTheme: true})(withWidth()(StepperVertical))));
|
||||
export default connect(mapStateToProps, { tutorialStep })(withRouter(withStyles(styles, {withTheme: true})(StepperVertical)));
|
||||
|
||||
@@ -1,35 +1,29 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { tutorialId, setTutorialLevel } from '../../actions/tutorialActions';
|
||||
import { tutorialId, tutorialStep } from '../../actions/tutorialActions';
|
||||
|
||||
import Breadcrumbs from '../Breadcrumbs';
|
||||
import StepperHorizontal from './StepperHorizontal';
|
||||
import StepperVertical from './StepperVertical';
|
||||
import Instruction from './Instruction';
|
||||
import BlocklyWindow from '../Blockly/BlocklyWindow';
|
||||
import SolutionCheck from './SolutionCheck';
|
||||
import CodeViewer from '../CodeViewer';
|
||||
import Assessment from './Assessment';
|
||||
import NotFound from '../NotFound';
|
||||
|
||||
import { tutorials } from './tutorials';
|
||||
import tutorials from './tutorials.json';
|
||||
|
||||
import withWidth, { isWidthUp } from '@material-ui/core/withWidth';
|
||||
import Tabs from '@material-ui/core/Tabs';
|
||||
import Tab from '@material-ui/core/Tab';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
||||
class Tutorial extends Component {
|
||||
|
||||
componentDidMount(){
|
||||
this.props.tutorialId(Number(this.props.match.params.tutorialId)-1);
|
||||
this.props.tutorialId(Number(this.props.match.params.tutorialId));
|
||||
}
|
||||
|
||||
componentDidUpdate(props, state){
|
||||
if(props.currentTutorialId+1 !== Number(this.props.match.params.tutorialId)){
|
||||
this.props.tutorialId(Number(this.props.match.params.tutorialId)-1);
|
||||
this.props.setTutorialLevel('instruction');
|
||||
if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){
|
||||
this.props.tutorialId(Number(this.props.match.params.tutorialId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,79 +31,55 @@ class Tutorial extends Component {
|
||||
this.props.tutorialId(null);
|
||||
}
|
||||
|
||||
onChange = (e, value) => {
|
||||
this.props.setTutorialLevel(value);
|
||||
}
|
||||
|
||||
render() {
|
||||
var currentTutorialId = this.props.currentTutorialId;
|
||||
console.log(this.props);
|
||||
var tutorial = tutorials.filter(tutorial => tutorial.id === currentTutorialId)[0];
|
||||
var steps = tutorial ? tutorial.steps : null;
|
||||
var step = steps ? steps[this.props.activeStep] : null;
|
||||
return (
|
||||
!Number.isInteger(currentTutorialId) || currentTutorialId+1 < 1 || currentTutorialId+1 > tutorials.length ?
|
||||
!Number.isInteger(currentTutorialId) || currentTutorialId < 1 || currentTutorialId > tutorials.length ?
|
||||
<NotFound button={{title: 'Zurück zur Tutorials-Übersicht', link: '/tutorial'}}/>
|
||||
:
|
||||
<div>
|
||||
<Breadcrumbs content={[{link: '/', title: 'Home'},{link: '/tutorial', title: 'Tutorial'}, {link: `/tutorial/${currentTutorialId+1}`, title: tutorials[currentTutorialId].title}]}/>
|
||||
<div>
|
||||
<Breadcrumbs content={[{link: '/', title: 'Home'},{link: '/tutorial', title: 'Tutorial'}, {link: `/tutorial/${currentTutorialId}`, title: tutorial.title}]}/>
|
||||
|
||||
<StepperHorizontal />
|
||||
<StepperHorizontal />
|
||||
|
||||
<div style={{display: 'flex'}}>
|
||||
<StepperVertical />
|
||||
<div style={{display: 'flex'}}>
|
||||
<StepperVertical steps={steps}/>
|
||||
{/* calc(Card-padding: 10px + Button-height: 35px + Button-marginTop: 15px)*/}
|
||||
<Card style={{padding: '10px 10px 60px 10px', display: 'block', position: 'relative', height: 'max-content', width: '100%'}}>
|
||||
{step ?
|
||||
step.type === 'instruction' ?
|
||||
<Instruction step={step}/>
|
||||
: <Assessment step={step}/> // if step.type === 'assessment'
|
||||
: null}
|
||||
|
||||
{/* width of vertical stepper is 30px*/}
|
||||
<Card style={{width: isWidthUp('sm', this.props.width) ? 'calc(100% - 30px)' : '100%', padding: '10px'}}>
|
||||
<Tabs
|
||||
value={this.props.level}
|
||||
indicatorColor="primary"
|
||||
textColor="inherit"
|
||||
variant='fullWidth'
|
||||
onChange={this.onChange}
|
||||
>
|
||||
<Tab label="Anleitung" value='instruction' disableRipple/>
|
||||
<Tab label="Aufgabe" value='assessment' disableRipple/>
|
||||
</Tabs>
|
||||
|
||||
<div style={{marginTop: '20px'}}>
|
||||
{this.props.level === 'instruction' ?
|
||||
<Instruction /> : null }
|
||||
{this.props.level === 'assessment' ?
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={6} lg={8} style={{ position: 'relative' }}>
|
||||
<SolutionCheck />
|
||||
<BlocklyWindow initialXml={this.props.status[currentTutorialId].xml ? this.props.status[currentTutorialId].xml : null}/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<Card style={{height: 'calc(50% - 30px)', padding: '10px', marginBottom: '10px'}}>
|
||||
Hier könnte die Problemstellung stehen.
|
||||
</Card>
|
||||
<div style={{height: '50%'}}>
|
||||
<CodeViewer />
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
: null }
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<div style={{marginTop: '20px', position: 'absolute', bottom: '10px'}}>
|
||||
<Button style={{marginRight: '10px', height: '35px'}} variant='contained' disabled={this.props.activeStep === 0} onClick={() => this.props.tutorialStep(this.props.activeStep-1)}>Zurück</Button>
|
||||
<Button style={{height: '35px'}}variant='contained' color='primary' disabled={this.props.activeStep === tutorial.steps.length-1} onClick={() => this.props.tutorialStep(this.props.activeStep+1)}>Weiter</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
Tutorial.propTypes = {
|
||||
tutorialId: PropTypes.func.isRequired,
|
||||
setTutorialLevel: PropTypes.func.isRequired,
|
||||
tutorialStep: PropTypes.func.isRequired,
|
||||
currentTutorialId: PropTypes.number,
|
||||
status: PropTypes.array.isRequired,
|
||||
change: PropTypes.number.isRequired,
|
||||
level: PropTypes.string.isRequired
|
||||
activeStep: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
change: state.tutorial.change,
|
||||
status: state.tutorial.status,
|
||||
currentTutorialId: state.tutorial.currentId,
|
||||
level: state.tutorial.level
|
||||
activeStep: state.tutorial.activeStep
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { tutorialId, setTutorialLevel })(withWidth()(Tutorial));
|
||||
export default connect(mapStateToProps, { tutorialId, tutorialStep })(Tutorial);
|
||||
|
||||
@@ -6,7 +6,7 @@ import clsx from 'clsx';
|
||||
|
||||
import Breadcrumbs from '../Breadcrumbs';
|
||||
|
||||
import { tutorials } from './tutorials';
|
||||
import tutorials from './tutorials.json';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
@@ -14,6 +14,7 @@ import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
@@ -21,20 +22,23 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
const styles = (theme) => ({
|
||||
outerDiv: {
|
||||
position: 'absolute',
|
||||
right: '-29px',
|
||||
bottom: '-29px',
|
||||
width: '140px',
|
||||
height: '140px',
|
||||
borderStyle: 'solid',
|
||||
borderWidth: '10px',
|
||||
borderRadius: '50%',
|
||||
borderColor: fade(theme.palette.primary.main, 0.2),
|
||||
color: fade(theme.palette.primary.main, 0.2)
|
||||
right: '-30px',
|
||||
bottom: '-30px',
|
||||
width: '160px',
|
||||
height: '160px',
|
||||
color: fade(theme.palette.secondary.main, 0.6)
|
||||
},
|
||||
outerDivError: {
|
||||
borderColor: fade(theme.palette.error.dark, 0.2),
|
||||
stroke: fade(theme.palette.error.dark, 0.2),
|
||||
color: fade(theme.palette.error.dark, 0.2)
|
||||
},
|
||||
outerDivSuccess: {
|
||||
stroke: fade(theme.palette.primary.main, 0.2),
|
||||
color: fade(theme.palette.primary.main, 0.2)
|
||||
},
|
||||
outerDivOther: {
|
||||
stroke: fade(theme.palette.secondary.main, 0.2)
|
||||
},
|
||||
innerDiv: {
|
||||
width: 'inherit',
|
||||
height: 'inherit',
|
||||
@@ -55,24 +59,38 @@ class TutorialHome extends Component {
|
||||
<h1>Tutorial-Übersicht</h1>
|
||||
<Grid container spacing={2}>
|
||||
{tutorials.map((tutorial, i) => {
|
||||
var tutorialStatus = this.props.status[i].status === 'success' ? 'Success' :
|
||||
this.props.status[i].status === 'error' ? 'Error' : 'Other';
|
||||
var steps = tutorial.steps;
|
||||
var tasks = steps.filter(task => task.type === 'task');
|
||||
var status = this.props.status.filter(status => status.id === tutorial.id)[0];
|
||||
var error = status.tasks.filter(task => task.type === 'error').length > 0;
|
||||
var success = status.tasks.filter(task => task.type === 'success').length/tasks.length
|
||||
var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other';
|
||||
return (
|
||||
<Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}>
|
||||
<Link to={`/tutorial/${i+1}`} style={{textDecoration: 'none', color: 'inherit'}}>
|
||||
<Link to={`/tutorial/${tutorial.id}`} style={{textDecoration: 'none', color: 'inherit'}}>
|
||||
<Paper style={{height: '150px', padding: '10px', position:'relative', overflow: 'hidden'}}>
|
||||
{tutorials[i].title}
|
||||
{tutorialStatus !== 'Other' ?
|
||||
<div className={clsx(this.props.classes.outerDiv, tutorialStatus === 'Error' ? this.props.classes.outerDivError : null)}>
|
||||
<div className={this.props.classes.innerDiv}>
|
||||
{tutorial.title}
|
||||
<div className={clsx(this.props.classes.outerDiv)} style={{width: '160px', height: '160px', border: 0}}>
|
||||
<svg style={{width: '100%', height: '100%'}}>
|
||||
{error || success === 1 ?
|
||||
<circle className={error ? this.props.classes.outerDivError : this.props.classes.outerDivSuccess} style={{transform: 'rotate(-44deg)', transformOrigin: "50% 50%"}} r="75" cx="50%" cy="50%" fill="none" stroke-width="10"></circle>
|
||||
: <circle className={this.props.classes.outerDivOther} style={{transform: 'rotate(-44deg)', transformOrigin: "50% 50%"}} r="75" cx="50%" cy="50%" fill="none" stroke-width="10" stroke-dashoffset={`${(75*2*Math.PI)*(1-(50/100+success/2))}`} stroke-dasharray={`${(75*2*Math.PI)*(1-(50/100-success/2))} ${(75*2*Math.PI)*(1-(50/100+success/2))}`}></circle>}
|
||||
{success < 1 && !error ?
|
||||
<circle className={this.props.classes.outerDivSuccess} style={{transform: 'rotate(-44deg)', transformOrigin: "50% 50%"}} r="75" cx="50%" cy="50%" fill="none" stroke-width="10" stroke-dashoffset={`${(75*2*Math.PI)*(1-(50/100+success/2))}`} stroke-dasharray={`${(75*2*Math.PI)}`}>
|
||||
</circle>
|
||||
: null}
|
||||
</svg>
|
||||
</div>
|
||||
<div className={clsx(this.props.classes.outerDiv, tutorialStatus === 'Error' ? this.props.classes.outerDivError : tutorialStatus === 'Success' ? this.props.classes.outerDivSuccess : null)}>
|
||||
<div className={this.props.classes.innerDiv}>
|
||||
{error || success === 1 ?
|
||||
<FontAwesomeIcon size='4x' icon={tutorialStatus === 'Success' ? faCheck : faTimes}/>
|
||||
</div>
|
||||
: <Typography variant='h3' className={success > 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success*100)}%</Typography>
|
||||
}
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
</Paper>
|
||||
</Link>
|
||||
|
||||
</Grid>
|
||||
)})}
|
||||
</Grid>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
export const checkXml = (originalXmlString, userXmlString) => {
|
||||
var originalXml = parseXml(originalXmlString);
|
||||
var userXml = parseXml(userXmlString);
|
||||
return compareXml(originalXml, userXml);
|
||||
};
|
||||
|
||||
const parseXml = (xmlString) => {
|
||||
var parser = new DOMParser();
|
||||
var xmlDoc = parser.parseFromString(xmlString, "text/xml");
|
||||
return xmlDoc;
|
||||
};
|
||||
|
||||
const compareNumberOfBlocks = (originalBlocks, userBlocks) => {
|
||||
if(originalBlocks.length !== userBlocks.length){
|
||||
if(originalBlocks.length > userBlocks.length){
|
||||
return {text: 'Es wurden zu wenig Blöcke verwendet.', type: 'error'};
|
||||
}
|
||||
else {
|
||||
return {text: 'Es wurden zu viele Blöcke verwendet.', type: 'error'};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const compareBlockType = (originalBlock, userBlock, index) => {
|
||||
if(originalBlock.attributes['type'].value !== userBlock.attributes['type'].value){
|
||||
return {text: `Es wurde ein falscher Blocktyp an Position ${index+1} verwendet`, type: 'error'};
|
||||
}
|
||||
};
|
||||
|
||||
const compareParentBlock = (originalBlock, userBlock, index) => {
|
||||
// using parentNode instead of parenElement
|
||||
// see https://stackoverflow.com/questions/8685739/difference-between-dom-parentnode-and-parentelement
|
||||
if(originalBlock.parentNode.attributes['name']){
|
||||
if(userBlock.parentNode.attributes['name']){
|
||||
// do the blocks have the same name-properties?
|
||||
if(originalBlock.parentNode.attributes['name'].value !== userBlock.parentNode.attributes['name'].value){
|
||||
if(userBlock.parentNode.attributes['name'].value === 'LOOP_FUNC' || userBlock.parentNode.attributes['name'].value === 'SETUP_FUNC'){
|
||||
return {text: `Der Block mit dem Typen '${userBlock.attributes['type'].value}' wurde irrtümlicherweise in die ${userBlock.parentNode.attributes['name'].value === 'SETUP_FUNC' ? 'Setup' : 'Endlosschleifen'}-Funktion geschrieben.
|
||||
Verschiebe den gesamten Block (und alle dazugehörigen Blöcke) in die ${userBlock.parentNode.attributes['name'].value !== 'SETUP_FUNC' ? 'Setup' : 'Endlosschleifen'}-Funktion.`, type: 'error'};
|
||||
}
|
||||
// TODO: has a block two name-properties?
|
||||
return {text: `Der Block mit dem Typen '${userBlock.attributes['type'].value}' hat ein falsches 'name'-Attribut`, type: 'error'};
|
||||
}
|
||||
}
|
||||
// user-block has not a name-attribute
|
||||
else {
|
||||
// do the user-block has a xmlns-attribute -> user-block is not connected
|
||||
if(userBlock.parentNode.attributes['xmlns']){
|
||||
return {text: `Der Block mit dem Typen '${userBlock.attributes['type'].value}' hat keine Verbindung zu einem anderen Block.`, type: 'error'};
|
||||
}
|
||||
// user-block has not a xmlns- AND name-attribute
|
||||
else {
|
||||
return {text: `Der Block an Position ${index+1} ist falsch eingeordnet. Tipp: Block an Position ${index+1} einem vorherigen Block unterordnen.`, type: 'error'};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const compareXml = (originalXml, userXml) => {
|
||||
var originalItemList = originalXml.getElementsByTagName("block");
|
||||
var userItemList = userXml.getElementsByTagName("block");
|
||||
|
||||
// compare number of blocks
|
||||
var number = compareNumberOfBlocks(originalItemList, userItemList);
|
||||
if(number){return number;}
|
||||
|
||||
for(var i=0; i < originalItemList.length; i++){
|
||||
// compare type
|
||||
var type = compareBlockType(originalItemList[i], userItemList[i], i);
|
||||
if(type){return type;}
|
||||
|
||||
// compare name
|
||||
var parent = compareParentBlock(originalItemList[i], userItemList[i], i);
|
||||
if(parent){return parent;}
|
||||
}
|
||||
|
||||
return {text: 'Super. Alles richtig!', type: 'success'};
|
||||
};
|
||||
@@ -18,6 +18,23 @@ export const tutorials = [
|
||||
</block>
|
||||
</xml>`
|
||||
},
|
||||
"solution": `<xml xmlns="https://developers.google.com/blockly/xml">
|
||||
<block type="arduino_functions" id="QWW|$jB8+*EL;}|#uA" deletable="false" x="37" y="20">
|
||||
<statement name="LOOP_FUNC">
|
||||
<block type="sensebox_telegram_do" id="K%yUabqRVQ{]9eX-8jZD">
|
||||
<statement name="telegram_do">
|
||||
<block type="controls_if" id="rA6:!p7,{y2MOuVpv[Pm">
|
||||
<value name="IF0">
|
||||
<block type="logic_boolean" id="=[Zh}O6_)fl?JD#2)2bL">
|
||||
<field name="BOOL">TRUE</field>
|
||||
</block>
|
||||
</value>
|
||||
</block>
|
||||
</statement>
|
||||
</block>
|
||||
</statement>
|
||||
</block>
|
||||
</xml>`,
|
||||
"test": function(workspace){
|
||||
var wifi = workspace.getBlocksByType('sensebox_wifi'); // result is an array with Blocks as objects
|
||||
if(wifi.length > 0){
|
||||
|
||||
@@ -42,7 +42,7 @@ class WorkspaceStats extends Component {
|
||||
style={{ marginRight: '1rem' }}
|
||||
color="primary"
|
||||
avatar={<Avatar><FontAwesomeIcon icon={faPlus} /></Avatar>}
|
||||
label={this.props.create > 0 ? this.props.create : 0}> // initialXML is created automatically, Block is not part of the statistics
|
||||
label={this.props.create > 0 ? this.props.create : 0}> {/* initialXML is created automatically, Block is not part of the statistics */}
|
||||
</Chip>
|
||||
</Tooltip>
|
||||
<Tooltip title="Anzahl veränderter Blöcke" >
|
||||
@@ -58,7 +58,7 @@ class WorkspaceStats extends Component {
|
||||
style={{ marginRight: '1rem' }}
|
||||
color="primary"
|
||||
avatar={<Avatar><FontAwesomeIcon icon={faArrowsAlt} /></Avatar>}
|
||||
label={this.props.move > 0 ? this.props.move : 0}> // initialXML is moved automatically, Block is not part of the statistics
|
||||
label={this.props.move > 0 ? this.props.move : 0}> {/* initialXML is moved automatically, Block is not part of the statistics */}
|
||||
</Chip>
|
||||
</Tooltip>
|
||||
<Tooltip title="Anzahl gelöschter Blöcke" >
|
||||
|
||||
Reference in New Issue
Block a user