adjustment of the tutorial component and all dependencies to the new tutorial.json

This commit is contained in:
Delucse 2020-09-11 10:13:33 +02:00
parent 33445cf67c
commit 6c3709fde8
14 changed files with 368 additions and 324 deletions

View File

@ -1,4 +1,4 @@
import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_LEVEL } from './types'; import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_STEP } from './types';
export const tutorialChange = () => (dispatch) => { export const tutorialChange = () => (dispatch) => {
dispatch({ dispatch({
@ -30,13 +30,6 @@ export const storeTutorialXml = (code) => (dispatch, getState) => {
} }
}; };
// level = "instruction" or "assessment"
export const setTutorialLevel = (level) => (dispatch) => {
dispatch({
type: TUTORIAL_LEVEL,
payload: level
});
}
export const tutorialId = (id) => (dispatch) => { export const tutorialId = (id) => (dispatch) => {
dispatch({ dispatch({
@ -44,3 +37,10 @@ export const tutorialId = (id) => (dispatch) => {
payload: id payload: id
}); });
}; };
export const tutorialStep = (step) => (dispatch) => {
dispatch({
type: TUTORIAL_STEP,
payload: step
});
};

View File

@ -12,4 +12,4 @@ export const TUTORIAL_ERROR = 'TUTORIAL_ERROR';
export const TUTORIAL_CHANGE = 'TUTORIAL_CHANGE'; export const TUTORIAL_CHANGE = 'TUTORIAL_CHANGE';
export const TUTORIAL_XML = 'TUTORIAL_XML'; export const TUTORIAL_XML = 'TUTORIAL_XML';
export const TUTORIAL_ID = 'TUTORIAL_ID'; export const TUTORIAL_ID = 'TUTORIAL_ID';
export const TUTORIAL_LEVEL = 'TUTORIAL_LEVEL'; export const TUTORIAL_STEP = 'TUTORIAL_STEP';

View File

@ -26,16 +26,18 @@ class BlocklyWindow extends Component {
this.props.onChangeWorkspace(event); this.props.onChangeWorkspace(event);
Blockly.Events.disableOrphans(event); Blockly.Events.disableOrphans(event);
}); });
Blockly.svgResize(workspace);
} }
componentDidUpdate(props) { // componentDidUpdate(props) {
if(props.initialXml !== this.props.initialXml){ // const workspace = Blockly.getMainWorkspace();
// guarantees that the current xml-code (this.props.initialXml) is rendered // if(props.initialXml !== this.props.initialXml){
const workspace = Blockly.getMainWorkspace(); // // guarantees that the current xml-code (this.props.initialXml) is rendered
workspace.clear(); // workspace.clear();
Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(this.props.initialXml), workspace); // Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(this.props.initialXml), workspace);
} // }
} // Blockly.svgResize(workspace);
// }
render() { render() {
return ( return (

View File

@ -0,0 +1,53 @@
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 currentTutorialId = this.props.currentTutorialId;
var step = this.props.step
return (
<div style={{width: '100%'}}>
<Typography variant='h4' style={{marginBottom: '5px'}}>{step.headline}</Typography>
<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'}}>
<Typography variant='h5'>Arbeitsauftrag</Typography>
<Typography>{step.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);

View File

@ -4,20 +4,20 @@ import { connect } from 'react-redux';
import BlocklyWindow from '../Blockly/BlocklyWindow'; import BlocklyWindow from '../Blockly/BlocklyWindow';
import { tutorials } from './tutorials';
import Grid from '@material-ui/core/Grid'; import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
class Instruction extends Component { class Instruction extends Component {
render() { render() {
var currentTutorialId = this.props.currentTutorialId; var step = this.props.step;
return ( return (
tutorials[currentTutorialId].instruction ?
<div> <div>
<p>{tutorials[currentTutorialId].instruction.description}</p> <Typography variant='h4' style={{marginBottom: '5px'}}>{step.headline}</Typography>
{tutorials[currentTutorialId].instruction.xml ? <Typography style={{marginBottom: '5px'}}>{step.text1}</Typography>
{step.hardware && step.hardware.length > 0 ? 'Hardware: todo' : null}
{step.requirements && step.requirements.length > 0 ? 'Voraussetzungen: todo' : null}
{step.xml ?
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12}> <Grid item xs={12}>
<BlocklyWindow <BlocklyWindow
@ -27,13 +27,12 @@ class Instruction extends Component {
grid={false} grid={false}
move={false} move={false}
blocklyCSS={{minHeight: '300px'}} blocklyCSS={{minHeight: '300px'}}
initialXml={tutorials[this.props.currentTutorialId].instruction.xml} initialXml={step.xml}
/> />
</Grid> </Grid>
</Grid> </Grid>
: null } : null }
</div> </div>
: null
); );
}; };
} }

View File

@ -3,11 +3,9 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { tutorialCheck } from '../../actions/tutorialActions'; import { tutorialCheck } from '../../actions/tutorialActions';
import * as Blockly from 'blockly/core';
import Compile from '../Compile'; import Compile from '../Compile';
import { tutorials } from './tutorials'; import tutorials from './tutorials.json';
import { checkXml } from './compareXml'; import { checkXml } from './compareXml';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
@ -49,15 +47,15 @@ class SolutionCheck extends Component {
} }
check = () => { check = () => {
const workspace = Blockly.getMainWorkspace(); const tutorial = tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0];
var msg = checkXml(tutorials[this.props.currentTutorialId].solution, this.props.xml); const step = tutorial.steps[this.props.activeStep];
var msg = checkXml(step.xml, this.props.xml);
this.props.tutorialCheck(msg.type); this.props.tutorialCheck(msg.type);
this.setState({ msg, open: true }); this.setState({ msg, open: true });
} }
render() { render() {
return ( return (
tutorials[this.props.currentTutorialId].test ?
<div> <div>
<Tooltip title='Lösung kontrollieren'> <Tooltip title='Lösung kontrollieren'>
<IconButton <IconButton
@ -93,7 +91,6 @@ class SolutionCheck extends Component {
</DialogActions> </DialogActions>
</Dialog> </Dialog>
</div> </div>
: null
); );
}; };
} }
@ -102,11 +99,13 @@ class SolutionCheck extends Component {
SolutionCheck.propTypes = { SolutionCheck.propTypes = {
tutorialCheck: PropTypes.func.isRequired, tutorialCheck: PropTypes.func.isRequired,
currentTutorialId: PropTypes.number, currentTutorialId: PropTypes.number,
activeStep: PropTypes.number.isRequired,
xml: PropTypes.string.isRequired xml: PropTypes.string.isRequired
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({
currentTutorialId: state.tutorial.currentId, currentTutorialId: state.tutorial.currentId,
activeStep: state.tutorial.activeStep,
xml: state.workspace.code.xml xml: state.workspace.code.xml
}); });

View File

@ -6,7 +6,7 @@ import { withRouter } from 'react-router-dom';
import clsx from 'clsx'; import clsx from 'clsx';
import { tutorials } from './tutorials'; import tutorials from './tutorials.json';
import { fade } from '@material-ui/core/styles/colorManipulator'; import { fade } from '@material-ui/core/styles/colorManipulator';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
@ -56,22 +56,22 @@ class StepperHorizontal extends Component {
return ( return (
<div className={clsx(this.props.classes.stepper, this.props.classes['stepper'+tutorialStatus])}> <div className={clsx(this.props.classes.stepper, this.props.classes['stepper'+tutorialStatus])}>
<Button <Button
disabled={tutorialId === 0} disabled={tutorialId === 1}
onClick={() => {this.props.history.push(`/tutorial/${tutorialId}`)}} onClick={() => {this.props.history.push(`/tutorial/${tutorialId-1}`)}}
> >
{'<'} {'<'}
</Button> </Button>
<Stepper activeStep={tutorialId+1} orientation="horizontal" <Stepper activeStep={tutorialId} orientation="horizontal"
style={{padding: 0}} classes={{root: this.props.classes.color}}> style={{padding: 0}} classes={{root: this.props.classes.color}}>
<Step expanded completed={false}> <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> : ''}> <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> <h1 style={{margin: 0}}>{tutorials.filter(tutorial => tutorial.id === tutorialId)[0].title}</h1>
</StepLabel> </StepLabel>
</Step> </Step>
</Stepper> </Stepper>
<Button <Button
disabled={tutorialId+2 > tutorials.length} disabled={tutorialId+1 > tutorials.length}
onClick={() => {this.props.history.push(`/tutorial/${tutorialId+2}`)}} onClick={() => {this.props.history.push(`/tutorial/${tutorialId+1}`)}}
> >
{'>'} {'>'}
</Button> </Button>

View File

@ -1,17 +1,13 @@
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 { tutorialStep } from '../../actions/tutorialActions';
import { withRouter, Link } from 'react-router-dom'; import { withRouter, Link } from 'react-router-dom';
import clsx from 'clsx'; import clsx from 'clsx';
import { tutorials } from './tutorials';
import { fade } from '@material-ui/core/styles/colorManipulator';
import { withStyles } from '@material-ui/core/styles'; 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 Stepper from '@material-ui/core/Stepper';
import Step from '@material-ui/core/Step'; import Step from '@material-ui/core/Step';
import StepLabel from '@material-ui/core/StepLabel'; import StepLabel from '@material-ui/core/StepLabel';
@ -26,158 +22,65 @@ const styles = (theme) => ({
borderStyle: `solid`, borderStyle: `solid`,
// borderWidth: '2px', // borderWidth: '2px',
borderRadius: '50%', borderRadius: '50%',
borderColor: theme.palette.secondary.main,
width: '12px', width: '12px',
height: '12px', height: '12px',
margin: '0 auto' margin: '0 auto',
},
stepIconMedium: {
width: '18px',
height: '18px',
}, },
stepIconLarge: { stepIconLarge: {
width: '24px', width: '24px',
height: '24px' height: '24px'
}, },
stepIconTransparent: { stepIconActive: {
borderColor: `transparent`, backgroundColor: theme.palette.secondary.main
cursor: 'default'
}, },
stepIconSuccess: { connector: {
borderColor: theme.palette.primary.main, height: '10px',
}, borderLeft: `3px solid ${theme.palette.primary.main}`,
stepIconError: { margin: '5px auto'
borderColor: theme.palette.error.dark,
},
stepIconOther: {
borderColor: theme.palette.secondary.main,
},
stepIconActiveSuccess: {
backgroundColor: fade(theme.palette.primary.main, 0.6)
},
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'
} }
}); });
class StepperVertical extends Component { class StepperVertical extends Component {
constructor(props){ componentDidMount(){
super(props); this.props.tutorialStep(0);
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
};
} }
componentDidUpdate(props){ componentDidUpdate(props){
if(props.currentTutorialId !== this.props.currentTutorialId){ if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){
this.setState({ this.props.tutorialStep(0);
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
});
} }
} }
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() { render() {
var tutorialId = this.props.currentTutorialId; var steps = this.props.steps;
var selectedVerticalTutorialId = this.state.selectedVerticalTutorialId; var activeStep = this.props.activeStep;
return ( return (
isWidthUp('sm', this.props.width) ?
<div style={{marginRight: '10px'}}> <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 <Stepper
activeStep={tutorialId+1} activeStep={activeStep}
orientation="vertical" orientation="vertical"
connector={<div style={{height: '10px'}}></div>} connector={<div className={this.props.classes.connector}></div>}
classes={{root: this.props.classes.verticalStepper}} classes={{root: this.props.classes.verticalStepper}}
> >
{this.state.tutorialArray.map((tutorial, i) => { {steps.map((step, i) => {
var index = this.state.tutorialArray.indexOf(tutorials[selectedVerticalTutorialId]); // var tutorialStatus = this.props.status[verticalTutorialId-1].status === 'success' ? 'Success' :
var verticalTutorialId = i === index ? selectedVerticalTutorialId+1 : selectedVerticalTutorialId+1 - index + i; // this.props.status[verticalTutorialId-1].status === 'error' ? 'Error' : 'Other';
var tutorialStatus = this.props.status[verticalTutorialId-1].status === 'success' ? 'Success' :
this.props.status[verticalTutorialId-1].status === 'error' ? 'Error' : 'Other';
return ( return (
<Step key={i}> <Step key={i}>
<Tooltip title={Object.keys(tutorial).length > 0 ? tutorial.title : ''} placement='right' arrow > <Tooltip title={step.headline} placement='right' arrow >
<Link to={`/tutorial/${verticalTutorialId}`}> <Link onClick={() => {this.props.tutorialStep(i)}}>
<StepLabel <StepLabel
StepIconComponent={'div'} StepIconComponent={'div'}
classes={{ classes={{
root: tutorial === tutorials[selectedVerticalTutorialId] ? root: step.type === 'task' ?
tutorial === tutorials[tutorialId] ? i === activeStep ?
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.stepIconActive)
: clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIcon'+tutorialStatus]) : clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge)
: tutorial === tutorials[selectedVerticalTutorialId-1] || tutorial === tutorials[selectedVerticalTutorialId+1] || : i === activeStep ?
tutorial === tutorials[verticalTutorialId-2] ? clsx(this.props.classes.stepIcon, this.props.classes.stepIconActive)
tutorial === tutorials[tutorialId] ? : clsx(this.props.classes.stepIcon)
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> </StepLabel>
@ -187,15 +90,6 @@ class StepperVertical extends Component {
)})} )})}
</Stepper> </Stepper>
</div> </div>
<Button
style={{minWidth: '30px', minHeight: '25px', padding: '0', writingMode: 'vertical-rl'}}
disabled={selectedVerticalTutorialId === tutorials.length-1}
onClick={() => {this.verticalStepper(1)}}
>
{'>'}
</Button>
</div>
: null
); );
}; };
} }
@ -204,13 +98,16 @@ class StepperVertical extends Component {
StepperVertical.propTypes = { StepperVertical.propTypes = {
status: PropTypes.array.isRequired, status: PropTypes.array.isRequired,
change: PropTypes.number.isRequired, change: PropTypes.number.isRequired,
currentTutorialId: PropTypes.number.isRequired currentTutorialId: PropTypes.number.isRequired,
activeStep: PropTypes.number.isRequired,
tutorialStep: PropTypes.func.isRequired
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({
change: state.tutorial.change, change: state.tutorial.change,
status: state.tutorial.status, 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)));

View File

@ -1,35 +1,29 @@
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 { tutorialId, setTutorialLevel } from '../../actions/tutorialActions'; import { tutorialId, tutorialStep } from '../../actions/tutorialActions';
import Breadcrumbs from '../Breadcrumbs'; import Breadcrumbs from '../Breadcrumbs';
import StepperHorizontal from './StepperHorizontal'; import StepperHorizontal from './StepperHorizontal';
import StepperVertical from './StepperVertical'; import StepperVertical from './StepperVertical';
import Instruction from './Instruction'; import Instruction from './Instruction';
import BlocklyWindow from '../Blockly/BlocklyWindow'; import Assessment from './Assessment';
import SolutionCheck from './SolutionCheck';
import CodeViewer from '../CodeViewer';
import NotFound from '../NotFound'; 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 Card from '@material-ui/core/Card';
import Button from '@material-ui/core/Button';
class Tutorial extends Component { class Tutorial extends Component {
componentDidMount(){ componentDidMount(){
this.props.tutorialId(Number(this.props.match.params.tutorialId)-1); this.props.tutorialId(Number(this.props.match.params.tutorialId));
} }
componentDidUpdate(props, state){ componentDidUpdate(props, state){
if(props.currentTutorialId+1 !== Number(this.props.match.params.tutorialId)){ if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){
this.props.tutorialId(Number(this.props.match.params.tutorialId)-1); this.props.tutorialId(Number(this.props.match.params.tutorialId));
this.props.setTutorialLevel('instruction');
} }
} }
@ -37,56 +31,33 @@ class Tutorial extends Component {
this.props.tutorialId(null); this.props.tutorialId(null);
} }
onChange = (e, value) => {
this.props.setTutorialLevel(value);
}
render() { render() {
var currentTutorialId = this.props.currentTutorialId; var currentTutorialId = this.props.currentTutorialId;
var tutorial = tutorials.filter(tutorial => tutorial.id === currentTutorialId)[0];
var steps = tutorial ? tutorial.steps : null;
var step = steps ? steps[this.props.activeStep] : null;
return ( 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'}}/> <NotFound button={{title: 'Zurück zur Tutorials-Übersicht', link: '/tutorial'}}/>
: :
<div> <div>
<Breadcrumbs content={[{link: '/', title: 'Home'},{link: '/tutorial', title: 'Tutorial'}, {link: `/tutorial/${currentTutorialId+1}`, title: tutorials[currentTutorialId].title}]}/> <Breadcrumbs content={[{link: '/', title: 'Home'},{link: '/tutorial', title: 'Tutorial'}, {link: `/tutorial/${currentTutorialId}`, title: tutorial.title}]}/>
<StepperHorizontal /> <StepperHorizontal />
<div style={{display: 'flex'}}> <div style={{display: 'flex'}}>
<StepperVertical /> <StepperVertical steps={steps}/>
{/* width of vertical stepper is 30px*/} <Card style={{padding: '10px'}}>
<Card style={{width: isWidthUp('sm', this.props.width) ? 'calc(100% - 30px)' : '100%', padding: '10px'}}> {step ?
<Tabs step.type === 'instruction' ?
value={this.props.level} <Instruction step={step}/>
indicatorColor="primary" : <Assessment step={step}/> // if step.type === 'assessment'
textColor="inherit" : null}
variant='fullWidth'
onChange={this.onChange}
>
<Tab label="Anleitung" value='instruction' disableRipple/>
<Tab label="Aufgabe" value='assessment' disableRipple/>
</Tabs>
<div style={{marginTop: '20px'}}> <div style={{marginTop: '20px'}}>
{this.props.level === 'instruction' ? <Button style={{marginRight: '10px'}} variant='contained' disabled={this.props.activeStep === 0} onClick={() => this.props.tutorialStep(this.props.activeStep-1)}>Zurück</Button>
<Instruction /> : null } <Button variant='contained' color='primary' disabled={this.props.activeStep === tutorial.steps.length-1} onClick={() => this.props.tutorialStep(this.props.activeStep+1)}>Weiter</Button>
{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> </div>
</Card> </Card>
</div> </div>
@ -97,18 +68,18 @@ class Tutorial extends Component {
Tutorial.propTypes = { Tutorial.propTypes = {
tutorialId: PropTypes.func.isRequired, tutorialId: PropTypes.func.isRequired,
setTutorialLevel: PropTypes.func.isRequired, tutorialStep: PropTypes.func.isRequired,
currentTutorialId: PropTypes.number, currentTutorialId: PropTypes.number,
status: PropTypes.array.isRequired, status: PropTypes.array.isRequired,
change: PropTypes.number.isRequired, change: PropTypes.number.isRequired,
level: PropTypes.string.isRequired activeStep: PropTypes.number.isRequired
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({
change: state.tutorial.change, change: state.tutorial.change,
status: state.tutorial.status, status: state.tutorial.status,
currentTutorialId: state.tutorial.currentId, 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);

View File

@ -0,0 +1,128 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { tutorialId, tutorialStep } from '../../actions/tutorialActions';
import Breadcrumbs from '../Breadcrumbs';
import StepperHorizontal from './StepperHorizontal';
import StepperVertical from './StepperVertical';
import Instruction from './Instruction';
import Assessment from './Assessment';
import NotFound from '../NotFound';
import tutorials from './tutorials.json';
import withWidth, { isWidthUp } from '@material-ui/core/withWidth';
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));
}
componentDidUpdate(props, state){
if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){
this.props.tutorialId(Number(this.props.match.params.tutorialId));
}
}
componentWillUnmount(){
this.props.tutorialId(null);
}
render() {
var currentTutorialId = this.props.currentTutorialId;
var tutorial = tutorials.filter(tutorial => tutorial.id === currentTutorialId)[0];
var step = tutorial.steps[this.props.activeStep];
const Tutorial2 = () => (
<div>
<Breadcrumbs content={[{link: '/', title: 'Home'},{link: '/tutorial', title: 'Tutorial'}, {link: `/tutorial/${currentTutorialId}`, title: tutorial.title}]}/>
<StepperHorizontal />
<div style={{display: 'flex'}}>
<StepperVertical />
<div>
{step.type === 'instruction' ?
<Instruction step={step}/>
: <Assessment step={step}/>}
<div style={{marginTop: '20px'}}>
<Button style={{marginRight: '10px'}} variant='contained' disabled={this.props.activeStep === 0} onClick={() => this.props.tutorialStep(this.props.activeStep-1)}>Zurück</Button>
<Button variant='contained' color='primary' disabled={this.props.activeStep === tutorial.steps.length-1} onClick={() => this.props.tutorialStep(this.props.activeStep+1)}>Weiter</Button>
</div>
</div>
</div>
</div>
);
return (
!Number.isInteger(currentTutorialId) || currentTutorialId < 1 || currentTutorialId > tutorials.length ?
<NotFound button={{title: 'Zurück zur Tutorials-Übersicht', link: '/tutorial'}}/>
:
<TutorialBody />
);
};
}
Tutorial.propTypes = {
tutorialId: PropTypes.func.isRequired,
currentTutorialId: PropTypes.number,
status: PropTypes.array.isRequired,
change: PropTypes.number.isRequired,
activeStep: PropTypes.number.isRequired
};
const mapStateToProps = state => ({
change: state.tutorial.change,
status: state.tutorial.status,
currentTutorialId: state.tutorial.currentId,
activeStep: state.tutorial.activeStep
});
export default connect(mapStateToProps, { tutorialId, tutorialStep })(withWidth()(Tutorial));
// <StepperHorizontal />
//
//
// {/* 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>

View File

@ -6,7 +6,7 @@ import clsx from 'clsx';
import Breadcrumbs from '../Breadcrumbs'; import Breadcrumbs from '../Breadcrumbs';
import { tutorials } from './tutorials'; import tutorials from './tutorials.json';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@ -59,9 +59,9 @@ class TutorialHome extends Component {
this.props.status[i].status === 'error' ? 'Error' : 'Other'; this.props.status[i].status === 'error' ? 'Error' : 'Other';
return ( return (
<Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}> <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'}}> <Paper style={{height: '150px', padding: '10px', position:'relative', overflow: 'hidden'}}>
{tutorials[i].title} {tutorial.title}
{tutorialStatus !== 'Other' ? {tutorialStatus !== 'Other' ?
<div className={clsx(this.props.classes.outerDiv, tutorialStatus === 'Error' ? this.props.classes.outerDivError : null)}> <div className={clsx(this.props.classes.outerDiv, tutorialStatus === 'Error' ? this.props.classes.outerDivError : null)}>
<div className={this.props.classes.innerDiv}> <div className={this.props.classes.innerDiv}>
@ -72,7 +72,6 @@ class TutorialHome extends Component {
} }
</Paper> </Paper>
</Link> </Link>
</Grid> </Grid>
)})} )})}
</Grid> </Grid>

View File

@ -4,38 +4,33 @@
"title": "Erste Schritte", "title": "Erste Schritte",
"steps": [ "steps": [
{ {
"id": 1,
"type": "instruction", "type": "instruction",
"headline": "Erste Schritte", "headline": "Erste Schritte",
"text1": "In diesem Tutorial lernst du die ersten Schritte mit der senseBox kennen. Du erstellst ein erstes Programm, baust einen ersten Schaltkreis auf und lernst, wie du das Programm auf die senseBox MCU überträgst", "text1": "In diesem Tutorial lernst du die ersten Schritte mit der senseBox kennen. Du erstellst ein erstes Programm, baust einen ersten Schaltkreis auf und lernst, wie du das Programm auf die senseBox MCU überträgst.",
"hardware": ["senseboxmcu", "led", "breadboard", "jst-adapter", "resistor"], "hardware": ["senseboxmcu", "led", "breadboard", "jst-adapter", "resistor"],
"requirements": [] "requirements": []
}, },
{ {
"id": 2,
"type": "instruction", "type": "instruction",
"headline": "Aufbau der Schaltung", "headline": "Aufbau der Schaltung",
"text1": "Stecke die LED auf das Breadboard und verbinde diese mithile des Widerstandes und dem JST Kabel mit dem Port Digital/Analog 1" "text1": "Stecke die LED auf das Breadboard und verbinde diese mithile des Widerstandes und dem JST Kabel mit dem Port Digital/Analog 1."
}, },
{ {
"id": 3,
"type": "instruction", "type": "instruction",
"headline": "Programmierung", "headline": "Programmierung",
"text1": "Jedes Programm für die senseBox besteht aus zwei Funktionen. Die Setup () Funktion wird zu Begin einmalig ausgeführt und der Programmcode Schrittweise ausgeführt. Nachdem die Setup () Funktion durchlaufen worden ist wird der Programmcode aus der zweiten Funktion, der Endlosschleife, fortlaufend wiederholt.", "text1": "Jedes Programm für die senseBox besteht aus zwei Funktionen. Die Setup () Funktion wird zu Begin einmalig ausgeführt und der Programmcode Schrittweise ausgeführt. Nachdem die Setup () Funktion durchlaufen worden ist wird der Programmcode aus der zweiten Funktion, der Endlosschleife, fortlaufend wiederholt.",
"xml": "<xml xmlns='https://developers.google.com/blockly/xml'><block type='arduino_functions' id='QWW|$jB8+*EL;}|#uA' deletable='false' x='27' y='16'></block></xml>" "xml": "<xml xmlns='https://developers.google.com/blockly/xml'><block type='arduino_functions' id='QWW|$jB8+*EL;}|#uA' deletable='false' x='27' y='16'></block></xml>"
}, },
{ {
"id": 4,
"type": "instruction", "type": "instruction",
"headline": "Leuchten der LED", "headline": "Leuchten der LED",
"text1": "Um nun die LED zum leuchten zu bringen wird folgender Block in die Endlosschleife eingefügt. Der Block bietet dir auszuwählen an welchen Pin die LED angeschlossen wurd und ob diese ein oder ausgeschaltet werden soll.", "text1": "Um nun die LED zum leuchten zu bringen wird folgender Block in die Endlosschleife eingefügt. Der Block bietet dir auszuwählen an welchen Pin die LED angeschlossen wurd und ob diese ein oder ausgeschaltet werden soll.",
"xml": "<xml xmlns='https://developers.google.com/blockly/xml'><block type='arduino_functions' id='QWW|$jB8+*EL;}|#uA' deletable='false' x='27' y='16'></block></xml>" "xml": "<xml xmlns='https://developers.google.com/blockly/xml'><block type='arduino_functions' id='QWW|$jB8+*EL;}|#uA' deletable='false' x='27' y='16'></block></xml>"
}, },
{ {
"id": 5,
"type": "task", "type": "task",
"headline": "Aufgabe 1", "headline": "Aufgabe 1",
"text1": "Verwenden den Block zum leuchten der LED und übertrage dein erstes Programm auf die senseBox MCU", "text1": "Verwenden den Block zum leuchten der LED und übertrage dein erstes Programm auf die senseBox MCU.",
"xml": "<xml xmlns='https://developers.google.com/blockly/xml'><block type='arduino_functions' id='QWW|$jB8+*EL;}|#uA' deletable='false' x='27' y='16'></block></xml>" "xml": "<xml xmlns='https://developers.google.com/blockly/xml'><block type='arduino_functions' id='QWW|$jB8+*EL;}|#uA' deletable='false' x='27' y='16'></block></xml>"
} }
] ]

View File

@ -42,7 +42,7 @@ class WorkspaceStats extends Component {
style={{ marginRight: '1rem' }} style={{ marginRight: '1rem' }}
color="primary" color="primary"
avatar={<Avatar><FontAwesomeIcon icon={faPlus} /></Avatar>} 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> </Chip>
</Tooltip> </Tooltip>
<Tooltip title="Anzahl veränderter Blöcke" > <Tooltip title="Anzahl veränderter Blöcke" >
@ -58,7 +58,7 @@ class WorkspaceStats extends Component {
style={{ marginRight: '1rem' }} style={{ marginRight: '1rem' }}
color="primary" color="primary"
avatar={<Avatar><FontAwesomeIcon icon={faArrowsAlt} /></Avatar>} 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> </Chip>
</Tooltip> </Tooltip>
<Tooltip title="Anzahl gelöschter Blöcke" > <Tooltip title="Anzahl gelöschter Blöcke" >

View File

@ -1,4 +1,4 @@
import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_LEVEL } from '../actions/types'; import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_STEP } from '../actions/types';
import { tutorials } from '../components/Tutorial/tutorials'; import { tutorials } from '../components/Tutorial/tutorials';
@ -7,7 +7,8 @@ const initialState = {
JSON.parse(window.localStorage.getItem('tutorial')) JSON.parse(window.localStorage.getItem('tutorial'))
: new Array(tutorials.length).fill({}), : new Array(tutorials.length).fill({}),
level: 'instruction', level: 'instruction',
currentId: null, currentId: 0,
activeStep: 0,
change: 0 change: 0
}; };
@ -32,10 +33,10 @@ export default function(state = initialState, action){
...state, ...state,
currentId: action.payload currentId: action.payload
} }
case TUTORIAL_LEVEL: case TUTORIAL_STEP:
return { return {
...state, ...state,
level: action.payload activeStep: action.payload
} }
default: default:
return state; return state;