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) => {
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) => {
dispatch({
@ -44,3 +37,10 @@ export const tutorialId = (id) => (dispatch) => {
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_XML = 'TUTORIAL_XML';
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);
Blockly.Events.disableOrphans(event);
});
Blockly.svgResize(workspace);
}
componentDidUpdate(props) {
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);
}
}
// componentDidUpdate(props) {
// const workspace = Blockly.getMainWorkspace();
// if(props.initialXml !== this.props.initialXml){
// // guarantees that the current xml-code (this.props.initialXml) is rendered
// workspace.clear();
// Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(this.props.initialXml), workspace);
// }
// Blockly.svgResize(workspace);
// }
render() {
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,36 +4,35 @@ import { connect } from 'react-redux';
import BlocklyWindow from '../Blockly/BlocklyWindow';
import { tutorials } from './tutorials';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
class Instruction extends Component {
render() {
var currentTutorialId = this.props.currentTutorialId;
var step = this.props.step;
return (
tutorials[currentTutorialId].instruction ?
<div>
<p>{tutorials[currentTutorialId].instruction.description}</p>
{tutorials[currentTutorialId].instruction.xml ?
<Grid container spacing={2}>
<Grid item xs={12}>
<BlocklyWindow
trashcan={false}
readOnly={true}
zoomControls={false}
grid={false}
move={false}
blocklyCSS={{minHeight: '300px'}}
initialXml={tutorials[this.props.currentTutorialId].instruction.xml}
/>
</Grid>
<div>
<Typography variant='h4' style={{marginBottom: '5px'}}>{step.headline}</Typography>
<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 item xs={12}>
<BlocklyWindow
trashcan={false}
readOnly={true}
zoomControls={false}
grid={false}
move={false}
blocklyCSS={{minHeight: '300px'}}
initialXml={step.xml}
/>
</Grid>
: null }
</div>
: null
</Grid>
: null }
</div>
);
};
}

View File

@ -3,11 +3,9 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { tutorialCheck } from '../../actions/tutorialActions';
import * as Blockly from 'blockly/core';
import Compile from '../Compile';
import { tutorials } from './tutorials';
import tutorials from './tutorials.json';
import { checkXml } from './compareXml';
import { withStyles } from '@material-ui/core/styles';
@ -49,51 +47,50 @@ class SolutionCheck extends Component {
}
check = () => {
const workspace = Blockly.getMainWorkspace();
var msg = checkXml(tutorials[this.props.currentTutorialId].solution, this.props.xml);
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);
this.setState({ msg, open: true });
}
render() {
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 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
<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>
</DialogActions>
</Dialog>
</div>
: null
</div>
: null}
</DialogContent>
<DialogActions>
<Button onClick={this.toggleDialog} color="primary">
Schließen
</Button>
</DialogActions>
</Dialog>
</div>
);
};
}
@ -102,11 +99,13 @@ class SolutionCheck extends Component {
SolutionCheck.propTypes = {
tutorialCheck: PropTypes.func.isRequired,
currentTutorialId: PropTypes.number,
activeStep: PropTypes.number.isRequired,
xml: PropTypes.string.isRequired
};
const mapStateToProps = state => ({
currentTutorialId: state.tutorial.currentId,
activeStep: state.tutorial.activeStep,
xml: state.workspace.code.xml
});

View File

@ -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';
@ -56,22 +56,22 @@ class StepperHorizontal extends Component {
return (
<div className={clsx(this.props.classes.stepper, this.props.classes['stepper'+tutorialStatus])}>
<Button
disabled={tutorialId === 0}
onClick={() => {this.props.history.push(`/tutorial/${tutorialId}`)}}
disabled={tutorialId === 1}
onClick={() => {this.props.history.push(`/tutorial/${tutorialId-1}`)}}
>
{'<'}
</Button>
<Stepper activeStep={tutorialId+1} orientation="horizontal"
<Stepper activeStep={tutorialId} 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>
<h1 style={{margin: 0}}>{tutorials.filter(tutorial => tutorial.id === tutorialId)[0].title}</h1>
</StepLabel>
</Step>
</Stepper>
<Button
disabled={tutorialId+2 > tutorials.length}
onClick={() => {this.props.history.push(`/tutorial/${tutorialId+2}`)}}
disabled={tutorialId+1 > tutorials.length}
onClick={() => {this.props.history.push(`/tutorial/${tutorialId+1}`)}}
>
{'>'}
</Button>

View File

@ -1,17 +1,13 @@
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 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,176 +22,74 @@ 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'
stepIconActive: {
backgroundColor: theme.palette.secondary.main
},
stepIconSuccess: {
borderColor: theme.palette.primary.main,
},
stepIconError: {
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'
connector: {
height: '10px',
borderLeft: `3px solid ${theme.palette.primary.main}`,
margin: '5px 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;
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 tutorialStatus = this.props.status[verticalTutorialId-1].status === 'success' ? 'Success' :
// this.props.status[verticalTutorialId-1].status === 'error' ? 'Error' : 'Other';
return (
<Step key={i}>
<Tooltip title={step.headline} placement='right' arrow >
<Link onClick={() => {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.stepIconActive)
: clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge)
: i === activeStep ?
clsx(this.props.classes.stepIcon, this.props.classes.stepIconActive)
: clsx(this.props.classes.stepIcon)
}}
>
</StepLabel>
</Link>
</Tooltip>
</Step>
)})}
</Stepper>
</div>
);
};
}
@ -204,13 +98,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)));

View File

@ -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,78 +31,55 @@ class Tutorial extends Component {
this.props.tutorialId(null);
}
onChange = (e, value) => {
this.props.setTutorialLevel(value);
}
render() {
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 (
!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}/>
{/* 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>
<Card style={{padding: '10px'}}>
{step ?
step.type === 'instruction' ?
<Instruction step={step}/>
: <Assessment step={step}/> // if step.type === 'assessment'
: null}
<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'}}>
<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>
</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);

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 { tutorials } from './tutorials';
import tutorials from './tutorials.json';
import { Link } from 'react-router-dom';
@ -59,9 +59,9 @@ class TutorialHome extends Component {
this.props.status[i].status === '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}
{tutorial.title}
{tutorialStatus !== 'Other' ?
<div className={clsx(this.props.classes.outerDiv, tutorialStatus === 'Error' ? this.props.classes.outerDivError : null)}>
<div className={this.props.classes.innerDiv}>
@ -72,7 +72,6 @@ class TutorialHome extends Component {
}
</Paper>
</Link>
</Grid>
)})}
</Grid>

View File

@ -4,38 +4,33 @@
"title": "Erste Schritte",
"steps": [
{
"id": 1,
"type": "instruction",
"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"],
"requirements": []
},
{
"id": 2,
"type": "instruction",
"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",
"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.",
"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",
"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.",
"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",
"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>"
}
]

View File

@ -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" >

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';
@ -7,7 +7,8 @@ const initialState = {
JSON.parse(window.localStorage.getItem('tutorial'))
: new Array(tutorials.length).fill({}),
level: 'instruction',
currentId: null,
currentId: 0,
activeStep: 0,
change: 0
};
@ -32,10 +33,10 @@ export default function(state = initialState, action){
...state,
currentId: action.payload
}
case TUTORIAL_LEVEL:
case TUTORIAL_STEP:
return {
...state,
level: action.payload
activeStep: action.payload
}
default:
return state;