update tutorial structure

This commit is contained in:
Mario
2020-10-08 15:29:28 +02:00
parent aaeaf7a5aa
commit 89d0cc380b
19 changed files with 420 additions and 411 deletions
+2 -1
View File
@@ -18,6 +18,7 @@ export const CHARACTER = {
export const BOOLEAN = {
typeId: 'Boolean',
typeName: 'Boolean',
typeMsgName: 'ARD_TYPE_BOOL',
}
@@ -89,7 +90,7 @@ const compatibleTypes = {
boolean: ['boolean'],
int: ['int'],
char: ['char'],
string: ['String'],
String: ['String'],
void: ['void'],
long: ['int', 'long'],
double: ['int', 'long', 'double'],
+45 -38
View File
@@ -33,7 +33,7 @@ const styles = (theme) => ({
class Builder extends Component {
constructor(props){
constructor(props) {
super(props);
this.state = {
open: false,
@@ -47,26 +47,33 @@ class Builder extends Component {
this.inputRef = React.createRef();
}
componentWillUnmount(){
componentWillUnmount() {
this.reset();
}
submit = () => {
console.log(this.props.id)
if (this.props.id === null) {
var randomID = Date.now();
} else {
randomID = this.props.id;
}
var isError = this.props.checkError();
if(isError){
this.setState({ snackbar: true, key: Date.now(), message: `Die Angaben für das Tutorial sind nicht vollständig.`, type: 'error'});
if (isError) {
this.setState({ snackbar: true, key: Date.now(), message: `Die Angaben für das Tutorial sind nicht vollständig.`, type: 'error' });
window.scrollTo(0, 0);
}
else{
else {
// export steps without attribute 'url'
var steps = this.props.steps.map(step => {
if(step.url){
if (step.url) {
delete step.url;
}
return step;
});
var tutorial = {
id: this.props.id,
id: randomID,
title: this.props.title,
steps: steps
}
@@ -77,15 +84,15 @@ class Builder extends Component {
reset = () => {
this.props.resetTutorial();
this.setState({ snackbar: true, key: Date.now(), message: `Das Tutorial wurde erfolgreich zurückgesetzt.`, type: 'success'});
this.setState({ snackbar: true, key: Date.now(), message: `Das Tutorial wurde erfolgreich zurückgesetzt.`, type: 'success' });
window.scrollTo(0, 0);
}
uploadJsonFile = (jsonFile) => {
this.props.progress(true);
if(jsonFile.type !== 'application/json'){
if (jsonFile.type !== 'application/json') {
this.props.progress(false);
this.setState({ open: true, string: false, title: 'Unzulässiger Dateityp', content: 'Die übergebene Datei entspricht nicht dem geforderten Format. Es sind nur JSON-Dateien zulässig.'});
this.setState({ open: true, string: false, title: 'Unzulässiger Dateityp', content: 'Die übergebene Datei entspricht nicht dem geforderten Format. Es sind nur JSON-Dateien zulässig.' });
}
else {
var reader = new FileReader();
@@ -97,27 +104,27 @@ class Builder extends Component {
}
uploadJsonString = () => {
this.setState({ open: true, string: true, title: 'JSON-String einfügen', content: ''});
this.setState({ open: true, string: true, title: 'JSON-String einfügen', content: '' });
}
readJson = (jsonString, isFile) => {
try {
var result = JSON.parse(jsonString);
if(!this.checkSteps(result.steps)){
if (!this.checkSteps(result.steps)) {
result.steps = [{}];
}
this.props.readJSON(result);
this.setState({ snackbar: true, key: Date.now(), message: `${isFile ? 'Die übergebene JSON-Datei' : 'Der übergebene JSON-String'} wurde erfolgreich übernommen.`, type: 'success'});
} catch(err){
this.setState({ snackbar: true, key: Date.now(), message: `${isFile ? 'Die übergebene JSON-Datei' : 'Der übergebene JSON-String'} wurde erfolgreich übernommen.`, type: 'success' });
} catch (err) {
console.log(err);
this.props.progress(false);
this.props.jsonString('');
this.setState({ open: true, string: false, title: 'Ungültiges JSON-Format', content: `${isFile ? 'Die übergebene Datei' : 'Der übergebene String'} enthält nicht valides JSON. Bitte überprüfe ${isFile ? 'die JSON-Datei' : 'den JSON-String'} und versuche es erneut.`});
this.setState({ open: true, string: false, title: 'Ungültiges JSON-Format', content: `${isFile ? 'Die übergebene Datei' : 'Der übergebene String'} enthält nicht valides JSON. Bitte überprüfe ${isFile ? 'die JSON-Datei' : 'den JSON-String'} und versuche es erneut.` });
}
}
checkSteps = (steps) => {
if(!(steps && steps.length > 0)){
if (!(steps && steps.length > 0)) {
return false;
}
return true;
@@ -131,41 +138,41 @@ class Builder extends Component {
render() {
return (
<div>
<Breadcrumbs content={[{link: '/tutorial', title: 'Tutorial'}, {link: '/tutorial/builder', title: 'Builder'}]}/>
<Breadcrumbs content={[{ link: '/tutorial', title: 'Tutorial' }, { link: '/tutorial/builder', title: 'Builder' }]} />
<h1>Tutorial-Builder</h1>
{/*upload JSON*/}
<div ref={this.inputRef}>
<input
style={{display: 'none'}}
style={{ display: 'none' }}
accept="application/json"
onChange={(e) => {this.uploadJsonFile(e.target.files[0])}}
onChange={(e) => { this.uploadJsonFile(e.target.files[0]) }}
id="open-json"
type="file"
/>
<label htmlFor="open-json">
<Button component="span" style={{marginRight: '10px', marginBottom: '10px'}} variant='contained' color='primary'>Datei laden</Button>
<Button component="span" style={{ marginRight: '10px', marginBottom: '10px' }} variant='contained' color='primary'>Datei laden</Button>
</label>
<Button style={{marginRight: '10px', marginBottom: '10px'}} variant='contained' color='primary' onClick={() => this.uploadJsonString()}>String laden</Button>
<Button style={{ marginRight: '10px', marginBottom: '10px' }} variant='contained' color='primary' onClick={() => this.uploadJsonString()}>String laden</Button>
</div>
<Divider variant='fullWidth' style={{margin: '10px 0 15px 0'}}/>
<Divider variant='fullWidth' style={{ margin: '10px 0 15px 0' }} />
{/*Tutorial-Builder-Form*/}
{this.props.error.type ?
<FormHelperText style={{lineHeight: 'initial'}} className={this.props.classes.errorColor}>{`Ein Tutorial muss mindestens jeweils eine Instruktion und eine Aufgabe enthalten.`}</FormHelperText>
: null}
<Id error={this.props.error.id} value={this.props.id}/>
<Textfield value={this.props.title} property={'title'} label={'Titel'} error={this.props.error.title}/>
<FormHelperText style={{ lineHeight: 'initial' }} className={this.props.classes.errorColor}>{`Ein Tutorial muss mindestens jeweils eine Instruktion und eine Aufgabe enthalten.`}</FormHelperText>
: null}
{/* <Id error={this.props.error.id} value={this.props.id} /> */}
<Textfield value={this.props.title} property={'title'} label={'Titel'} error={this.props.error.title} />
{this.props.steps.map((step, i) =>
<Step step={step} index={i} key={i}/>
<Step step={step} index={i} key={i} />
)}
{/*submit or reset*/}
<Divider variant='fullWidth' style={{margin: '30px 0 10px 0'}}/>
<Button style={{marginRight: '10px', marginTop: '10px'}} variant='contained' color='primary' onClick={() => this.submit()}>Tutorial-Vorlage erstellen</Button>
<Button style={{marginTop: '10px'}} variant='contained' onClick={() => this.reset()}>Zurücksetzen</Button>
<Divider variant='fullWidth' style={{ margin: '30px 0 10px 0' }} />
<Button style={{ marginRight: '10px', marginTop: '10px' }} variant='contained' color='primary' onClick={() => this.submit()}>Tutorial-Vorlage erstellen</Button>
<Button style={{ marginTop: '10px' }} variant='contained' onClick={() => this.reset()}>Zurücksetzen</Button>
<Backdrop className={this.props.classes.backdrop} open={this.props.isProgress}>
<CircularProgress color="inherit" />
@@ -182,16 +189,16 @@ class Builder extends Component {
button={'Schließen'}
actions={
this.state.string ?
<div>
<Button disabled={this.props.error.json || this.props.json === ''} variant='contained' onClick={() => {this.toggle(); this.props.progress(true); this.readJson(this.props.json, false);}} color="primary">Bestätigen</Button>
<Button onClick={() => {this.toggle(); this.props.jsonString('');}} color="primary">Abbrechen</Button>
</div>
: null
<div>
<Button disabled={this.props.error.json || this.props.json === ''} variant='contained' onClick={() => { this.toggle(); this.props.progress(true); this.readJson(this.props.json, false); }} color="primary">Bestätigen</Button>
<Button onClick={() => { this.toggle(); this.props.jsonString(''); }} color="primary">Abbrechen</Button>
</div>
: null
}
>
{this.state.string ?
<Textfield value={this.props.json} property={'json'} label={'JSON'} multiline error={this.props.error.json}/>
: null}
<Textfield value={this.props.json} property={'json'} label={'JSON'} multiline error={this.props.error.json} />
: null}
</Dialog>
<Snackbar
@@ -230,4 +237,4 @@ const mapStateToProps = state => ({
isProgress: state.builder.progress
});
export default connect(mapStateToProps, { checkError, readJSON, jsonString, progress, resetTutorial })(withStyles(styles, {withTheme: true})(Builder));
export default connect(mapStateToProps, { checkError, readJSON, jsonString, progress, resetTutorial })(withStyles(styles, { withTheme: true })(Builder));
+56 -54
View File
@@ -28,81 +28,83 @@ const styles = theme => ({
class Id extends Component {
handleChange = (e) => {
var value = parseInt(e.target.value);
if(Number.isInteger(value) && value > 0){
var value =
parseInt(e.target.value);
if (Number.isInteger(value) && value > 0) {
this.props.tutorialId(value);
if(this.props.error){
if (this.props.error) {
this.props.deleteError(undefined, 'id');
}
}
else {
this.props.tutorialId(value.toString());
this.props.setError(undefined,'id');
this.props.setError(undefined, 'id');
}
};
handleCounter = (step) => {
if(this.props.value+step < 1){
this.props.setError(undefined,'id');
if (this.props.value + step < 1) {
this.props.setError(undefined, 'id');
}
else if(this.props.error){
else if (this.props.error) {
this.props.deleteError(undefined, 'id');
}
if(!this.props.value || !Number.isInteger(this.props.value)){
this.props.tutorialId(0+step);
if (!this.props.value || !Number.isInteger(this.props.value)) {
this.props.tutorialId(0 + step);
}
else {
this.props.tutorialId(this.props.value+step);
this.props.tutorialId(this.props.value + step);
}
}
render() {
return (
<div style={{display: 'inline-flex', marginTop: '15px'}}>
<FormControl variant="outlined" style={{marginBottom: '10px', width: '250px'}}>
<InputLabel
htmlFor="id"
classes={{shrink: this.props.error ? this.props.classes.errorColorShrink : null}}
>
ID
<div style={{ display: 'inline-flex', marginTop: '15px' }}>
<FormControl variant="outlined" style={{ marginBottom: '10px', width: '250px' }}>
<InputLabel
htmlFor="id"
classes={{ shrink: this.props.error ? this.props.classes.errorColorShrink : null }}
>
ID
</InputLabel>
<OutlinedInput
style={{borderRadius: '25px', padding: '0 0 0 10px', width: '200px'}}
classes={{notchedOutline: this.props.error ? this.props.classes.errorBorder : null}}
error={this.props.error}
value={this.props.value}
name='id'
label='ID'
id='id'
onChange={this.handleChange}
inputProps={{
style: {marginRight: '10px'}
}}
endAdornment={
<div style={{display: 'flex'}}>
<Button
disabled={this.props.value === 1 || !Number.isInteger(this.props.value)}
onClick={() => this.handleCounter(-1)}
variant='contained'
color='primary'
style={{borderRadius: '25px 0 0 25px', height: '56px', boxShadow: '0 0 transparent'}}
>
<FontAwesomeIcon icon={faMinus} />
</Button>
<Button
onClick={() => this.handleCounter(1)}
variant='contained'
color='primary'
style={{borderRadius: '0 25px 25px 0', height: '56px', boxShadow: '0 0 transparent'}}
>
<FontAwesomeIcon icon={faPlus} />
</Button>
</div>
}
/>
{this.props.error ? <FormHelperText className={this.props.classes.errorColor}>Gib eine positive ganzzahlige Zahl ein.</FormHelperText> : null}
</FormControl>
<FormHelperText style={{marginLeft: '-40px', marginTop: '5px', lineHeight: 'initial', marginBottom: '10px', width: '200px'}}>Beachte, dass die ID eindeutig sein muss. Sie muss sich also zu den anderen Tutorials unterscheiden.</FormHelperText>
<OutlinedInput
style={{ borderRadius: '25px', padding: '0 0 0 10px', width: '200px' }}
classes={{ notchedOutline: this.props.error ? this.props.classes.errorBorder : null }}
error={this.props.error}
value={this.props.value}
name='id'
label='ID'
id='id'
onChange={this.handleChange}
inputProps={{
style: { marginRight: '10px' }
}}
endAdornment={
<div style={{ display: 'flex' }}>
<Button
disabled={this.props.value === 1 || !Number.isInteger(this.props.value)}
onClick={() => this.handleCounter(-1)}
variant='contained'
color='primary'
style={{ borderRadius: '25px 0 0 25px', height: '56px', boxShadow: '0 0 transparent' }}
>
<FontAwesomeIcon icon={faMinus} />
</Button>
<Button
onClick={() => this.handleCounter(1)}
variant='contained'
color='primary'
style={{ borderRadius: '0 25px 25px 0', height: '56px', boxShadow: '0 0 transparent' }}
>
<FontAwesomeIcon icon={faPlus} />
</Button>
</div>
}
/>
{this.props.error ? <FormHelperText className={this.props.classes.errorColor}>Gib eine positive ganzzahlige Zahl ein.</FormHelperText> : null}
</FormControl>
<FormHelperText style={{ marginLeft: '-40px', marginTop: '5px', lineHeight: 'initial', marginBottom: '10px', width: '200px' }}>Beachte, dass die ID eindeutig sein muss. Sie muss sich also zu den anderen Tutorials unterscheiden.</FormHelperText>
</div>
);
};
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { changeContent } from '../../../actions/tutorialBuilderActions';
import tutorials from '../../../data/tutorials.json';
import tutorials from '../../../data/tutorials';
import FormGroup from '@material-ui/core/FormGroup';
import Checkbox from '@material-ui/core/Checkbox';
@@ -17,7 +17,7 @@ class Requirements extends Component {
onChange = (e) => {
var requirements = this.props.value;
var value = parseInt(e.target.value)
if(e.target.checked){
if (e.target.checked) {
requirements.push(value);
}
else {
@@ -28,9 +28,9 @@ class Requirements extends Component {
render() {
return (
<FormControl style={{marginBottom: '10px', padding: '18.5px 14px', borderRadius: '25px', border: '1px solid lightgrey', width: 'calc(100% - 28px)'}}>
<FormLabel style={{color: 'black'}}>Voraussetzungen</FormLabel>
<FormHelperText style={{marginTop: '5px'}}>Beachte, dass die Reihenfolge des Anhakens maßgebend ist.</FormHelperText>
<FormControl style={{ marginBottom: '10px', padding: '18.5px 14px', borderRadius: '25px', border: '1px solid lightgrey', width: 'calc(100% - 28px)' }}>
<FormLabel style={{ color: 'black' }}>Voraussetzungen</FormLabel>
<FormHelperText style={{ marginTop: '5px' }}>Beachte, dass die Reihenfolge des Anhakens maßgebend ist.</FormHelperText>
<FormGroup>
{tutorials.map((tutorial, i) =>
<FormControlLabel
+30 -28
View File
@@ -28,7 +28,8 @@ const styles = theme => ({
color: theme.palette.text.primary
},
multiGridListTile: {
background: fade(theme.palette.secondary.main, 0.5),
background: "#4EAF47",
opacity: 0.9,
height: '30px'
},
multiGridListTileTitle: {
@@ -45,46 +46,47 @@ class Hardware extends Component {
};
handleClickOpen = (hardwareInfo) => {
this.setState({open: true, hardwareInfo});
this.setState({ open: true, hardwareInfo });
};
handleClose = () => {
this.setState({open: false, hardwareInfo: {}});
this.setState({ open: false, hardwareInfo: {} });
};
render() {
var cols = isWidthDown('md', this.props.width) ? isWidthDown('sm', this.props.width) ? isWidthDown('xs', this.props.width) ? 2 : 3 : 4 : 6;
return (
<div style={{marginTop: '10px', marginBottom: '5px'}}>
<div style={{ marginTop: '10px', marginBottom: '5px' }}>
<Typography>Für die Umsetzung benötigst du folgende Hardware:</Typography>
<GridList cellHeight={100} cols={cols} spacing={10}>
{this.props.picture.map((picture,i) => {
var hardwareInfo = hardware.filter(hardware => hardware.id === picture)[0];
return(
<GridListTile key={i}>
<div style={{margin: 'auto', width: 'max-content'}}>
<img src={`/media/hardware/${hardwareInfo.src}`} alt={hardwareInfo.name} height={100} style={{cursor: 'pointer'}} onClick={() => this.handleClickOpen(hardwareInfo)}/>
</div>
<GridListTileBar
classes={{root: this.props.classes.multiGridListTile}}
title={
<div style={{overflow: 'hidden', textOverflow: 'ellipsis'}} className={this.props.classes.multiGridListTileTitle}>
{hardwareInfo.name}
</div>
}
actionIcon={
<IconButton className={this.props.classes.expand} aria-label='Vollbild' onClick={() => this.handleClickOpen(hardwareInfo)}>
<FontAwesomeIcon icon={faExpandAlt} size="xs"/>
</IconButton>
}
/>
</GridListTile>
)})}
{this.props.picture.map((picture, i) => {
var hardwareInfo = hardware.filter(hardware => hardware.id === picture)[0];
return (
<GridListTile key={i}>
<div style={{ margin: 'auto', width: 'max-content' }}>
<img src={`/media/hardware/${hardwareInfo.src}`} alt={hardwareInfo.name} height={100} style={{ cursor: 'pointer' }} onClick={() => this.handleClickOpen(hardwareInfo)} />
</div>
<GridListTileBar
classes={{ root: this.props.classes.multiGridListTile }}
title={
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }} className={this.props.classes.multiGridListTileTitle}>
{hardwareInfo.name}
</div>
}
actionIcon={
<IconButton className={this.props.classes.expand} aria-label='Vollbild' onClick={() => this.handleClickOpen(hardwareInfo)}>
<FontAwesomeIcon icon={faExpandAlt} size="xs" />
</IconButton>
}
/>
</GridListTile>
)
})}
</GridList>
<Dialog
style={{zIndex: 1500}}
style={{ zIndex: 1500 }}
open={this.state.open}
title={`Hardware: ${this.state.hardwareInfo.name}`}
content={this.state.content}
@@ -93,7 +95,7 @@ class Hardware extends Component {
button={'Schließen'}
>
<div>
<img src={`/media/hardware/${this.state.hardwareInfo.src}`} width="100%" alt={this.state.hardwareInfo.name}/>
<img src={`/media/hardware/${this.state.hardwareInfo.src}`} width="100%" alt={this.state.hardwareInfo.name} />
Weitere Informationen zur Hardware-Komponente findest du <Link href={this.state.hardwareInfo.url} color="primary">hier</Link>.
</div>
</Dialog>
+18 -17
View File
@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import clsx from 'clsx';
import { withRouter, Link } from 'react-router-dom';
import tutorials from '../../data/tutorials.json';
import tutorials from '../../data/tutorials';
import { fade } from '@material-ui/core/styles/colorManipulator';
import { withStyles } from '@material-ui/core/styles';
@@ -63,46 +63,47 @@ class Requirement extends Component {
render() {
var tutorialIds = this.props.tutorialIds;
return (
<div style={{marginTop: '20px', marginBottom: '5px'}}>
<Typography>Es bietet sich an folgende Tutorials vorab erfolgreich gelöst zu haben:</Typography>
<div style={{ marginTop: '20px', marginBottom: '5px' }}>
<Typography>Bevor du mit diesem Tutorial fortfährst solltest du folgende Tutorials erfolgreich abgeschlossen haben:</Typography>
<List component="div">
{tutorialIds.map((tutorialId, i) => {
var title = tutorials.filter(tutorial => tutorial.id === tutorialId)[0].title;
var status = this.props.status.filter(status => status.id === tutorialId)[0];
var tasks = status.tasks;
var error = status.tasks.filter(task => task.type === 'error').length > 0;
var success = status.tasks.filter(task => task.type === 'success').length/tasks.length
var success = status.tasks.filter(task => task.type === 'success').length / tasks.length
var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other';
return(
return (
<Link to={`/tutorial/${tutorialId}`} className={this.props.classes.link}>
<Tooltip style={{height: '50px', width: '50px', position: 'absolute', background: 'white', zIndex: 1, borderRadius: '25px'}} title={error ? `Mind. eine Aufgabe im Tutorial wurde nicht richtig gelöst.` : success === 1 ? `Das Tutorial wurde bereits erfolgreich abgeschlossen.` : `Das Tutorial ist zu ${success * 100}% abgeschlossen.`} arrow>
<Tooltip style={{ height: '50px', width: '50px', position: 'absolute', background: 'white', zIndex: 1, borderRadius: '25px' }} title={error ? `Mind. eine Aufgabe im Tutorial wurde nicht richtig gelöst.` : success === 1 ? `Das Tutorial wurde bereits erfolgreich abgeschlossen.` : `Das Tutorial ist zu ${success * 100}% abgeschlossen.`} arrow>
<div>
<div className={clsx(this.props.classes.outerDiv)} style={{width: '50px', height: '50px', border: 0}}>
<svg style={{width: '100%', height: '100%'}}>
<div className={clsx(this.props.classes.outerDiv)} style={{ width: '50px', height: '50px', border: 0 }}>
<svg style={{ width: '100%', height: '100%' }}>
{error || success === 1 ?
<circle className={error ? this.props.classes.outerDivError : this.props.classes.outerDivSuccess} r="22.5" cx="50%" cy="50%" fill="none" stroke-width="5"></circle>
: <circle className={this.props.classes.outerDivOther} r="22.5" cx="50%" cy="50%" fill="none" stroke-width="5"></circle>}
: <circle className={this.props.classes.outerDivOther} r="22.5" cx="50%" cy="50%" fill="none" stroke-width="5"></circle>}
{success < 1 && !error ?
<circle className={this.props.classes.outerDivSuccess} style={{transform: 'rotate(-90deg)', transformOrigin: "50% 50%"}} r="22.5" cx="50%" cy="50%" fill="none" stroke-width="5" stroke-dashoffset={`${(22.5*2*Math.PI)*(1-success)}`} stroke-dasharray={`${(22.5*2*Math.PI)}`}>
<circle className={this.props.classes.outerDivSuccess} style={{ transform: 'rotate(-90deg)', transformOrigin: "50% 50%" }} r="22.5" cx="50%" cy="50%" fill="none" stroke-width="5" stroke-dashoffset={`${(22.5 * 2 * Math.PI) * (1 - success)}`} stroke-dasharray={`${(22.5 * 2 * Math.PI)}`}>
</circle>
: null}
: 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 icon={tutorialStatus === 'Success' ? faCheck : faTimes}/>
: <Typography variant='h7' className={success > 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success*100)}%</Typography>
<FontAwesomeIcon icon={tutorialStatus === 'Success' ? faCheck : faTimes} />
: <Typography variant='h7' className={success > 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success * 100)}%</Typography>
}
</div>
</div>
</div>
</Tooltip>
<div style={{height: '50px', width: 'calc(100% - 25px)', transform: 'translate(25px)'}} className={this.props.classes.hoverLink}>
<Typography style={{margin: 0, position: 'absolute', top: '50%', transform: 'translate(45px, -50%)', maxHeight: '50px', overflow: 'hidden', maxWidth: 'calc(100% - 45px)'/*, textOverflow: 'ellipsis', whiteSpace: 'pre-line', overflowWrap: 'anywhere'*/}}>{title}</Typography>
<div style={{ height: '50px', width: 'calc(100% - 25px)', transform: 'translate(25px)' }} className={this.props.classes.hoverLink}>
<Typography style={{ margin: 0, position: 'absolute', top: '50%', transform: 'translate(45px, -50%)', maxHeight: '50px', overflow: 'hidden', maxWidth: 'calc(100% - 45px)'/*, textOverflow: 'ellipsis', whiteSpace: 'pre-line', overflowWrap: 'anywhere'*/ }}>{title}</Typography>
</div>
</Link>
)}
)
}
)}
</List>
</div>
@@ -120,4 +121,4 @@ const mapStateToProps = state => ({
status: state.tutorial.status
});
export default connect(mapStateToProps, null)(withStyles(styles, {withTheme: true})(withRouter(Requirement)));
export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(withRouter(Requirement)));
+16 -16
View File
@@ -8,7 +8,7 @@ import { withRouter } from 'react-router-dom';
import Compile from '../Compile';
import Dialog from '../Dialog';
import tutorials from '../../data/tutorials.json';
import tutorials from '../../data/tutorials';
import { checkXml } from '../../helpers/compareXml';
import { withStyles } from '@material-ui/core/styles';
@@ -32,16 +32,16 @@ const styles = (theme) => ({
class SolutionCheck extends Component {
state={
state = {
open: false,
msg: ''
}
toggleDialog = () => {
if(this.state.open){
if (this.state.open) {
this.setState({ open: false, msg: '' });
}
else{
else {
this.setState({ open: !this.state });
}
}
@@ -61,15 +61,15 @@ class SolutionCheck extends Component {
<Tooltip title='Lösung kontrollieren' arrow>
<IconButton
className={this.props.classes.compile}
style={{width: '40px', height: '40px', marginRight: '5px'}}
style={{ width: '40px', height: '40px', marginRight: '5px' }}
onClick={() => this.check()}
>
<FontAwesomeIcon icon={faPlay} size="xs"/>
<FontAwesomeIcon icon={faPlay} size="xs" />
</IconButton>
</Tooltip>
<Dialog
style={{zIndex: 9999999}}
style={{ zIndex: 9999999 }}
fullWidth
maxWidth={'sm'}
open={this.state.open}
@@ -80,29 +80,29 @@ class SolutionCheck extends Component {
button={'Schließen'}
>
{this.state.msg.type === 'success' ?
<div style={{marginTop: '20px', display: 'flex'}}>
<div style={{ marginTop: '20px', display: 'flex' }}>
<Compile />
{this.props.activeStep === steps.length-1 ?
{this.props.activeStep === steps.length - 1 ?
<Button
style={{marginLeft: '10px'}}
style={{ marginLeft: '10px' }}
variant="contained"
color="primary"
onClick={() => {this.toggleDialog(); this.props.history.push(`/tutorial/`)}}
onClick={() => { this.toggleDialog(); this.props.history.push(`/tutorial/`) }}
>
Tutorials-Übersicht
</Button>
:
:
<Button
style={{marginLeft: '10px'}}
style={{ marginLeft: '10px' }}
variant="contained"
color="primary"
onClick={() => {this.toggleDialog(); this.props.tutorialStep(this.props.activeStep + 1)}}
onClick={() => { this.toggleDialog(); this.props.tutorialStep(this.props.activeStep + 1) }}
>
nächster Schritt
</Button>
}
</div>
: null}
: null}
</Dialog>
</div>
@@ -125,4 +125,4 @@ const mapStateToProps = state => ({
xml: state.workspace.code.xml
});
export default connect(mapStateToProps, { tutorialCheck, tutorialStep })(withStyles(styles, {withTheme: true})(withRouter(SolutionCheck)));
export default connect(mapStateToProps, { tutorialCheck, tutorialStep })(withStyles(styles, { withTheme: true })(withRouter(SolutionCheck)));
+13 -13
View File
@@ -6,7 +6,7 @@ import { withRouter } from 'react-router-dom';
import clsx from 'clsx';
import tutorials from '../../data/tutorials.json';
import tutorials from '../../data/tutorials';
import { fade } from '@material-ui/core/styles/colorManipulator';
import { withStyles } from '@material-ui/core/styles';
@@ -58,31 +58,31 @@ class StepperHorizontal extends Component {
var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other';
var title = tutorials.filter(tutorial => tutorial.id === tutorialId)[0].title;
return (
<div style={{position: 'relative'}}>
<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 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}
: 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 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}
: null}
<div className={this.props.classes.stepper}>
<Button
disabled={tutorialId === 1}
onClick={() => {this.props.history.push(`/tutorial/${tutorialId-1}`)}}
onClick={() => { this.props.history.push(`/tutorial/${tutorialId - 1}`) }}
>
{'<'}
</Button>
<Tooltip style={{display: 'flex', width: 'calc(100% - 64px - 64px)', justifyContent: 'center'}} title={title} arrow>
<Tooltip style={{ display: 'flex', width: 'calc(100% - 64px - 64px)', justifyContent: 'center' }} title={title} arrow>
<div>
{tutorialStatus !== 'Other' ? <div className={tutorialStatus === 'Success' && success === 1 ? this.props.classes.iconDivSuccess : this.props.classes.iconDivError} style={{margin: 'auto 10px auto 0'}}><FontAwesomeIcon className={this.props.classes.icon} icon={tutorialStatus === 'Success' ? faCheck : faTimes}/></div> : null}
<Typography variant='body2' style={{fontWeight: 'bold', fontSize: '1.75em', margin: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'rgba(0, 0, 0, 0.54)'}}>{title}</Typography>
{tutorialStatus !== 'Other' ? <div className={tutorialStatus === 'Success' && success === 1 ? this.props.classes.iconDivSuccess : this.props.classes.iconDivError} style={{ margin: 'auto 10px auto 0' }}><FontAwesomeIcon className={this.props.classes.icon} icon={tutorialStatus === 'Success' ? faCheck : faTimes} /></div> : null}
<Typography variant='body2' style={{ fontWeight: 'bold', fontSize: '1.75em', margin: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'rgba(0, 0, 0, 0.54)' }}>{title}</Typography>
</div>
</Tooltip>
<Button
disabled={tutorialId+1 > tutorials.length}
onClick={() => {this.props.history.push(`/tutorial/${tutorialId+1}`)}}
disabled={tutorialId + 1 > tutorials.length}
onClick={() => { this.props.history.push(`/tutorial/${tutorialId + 1}`) }}
>
{'>'}
</Button>
@@ -104,4 +104,4 @@ const mapStateToProps = state => ({
currentTutorialId: state.tutorial.currentId
});
export default connect(mapStateToProps, null)(withRouter(withStyles(styles, {withTheme: true})(StepperHorizontal)));
export default connect(mapStateToProps, null)(withRouter(withStyles(styles, { withTheme: true })(StepperHorizontal)));
+25 -25
View File
@@ -13,24 +13,24 @@ import NotFound from '../NotFound';
import { detectWhitespacesAndReturnReadableResult } from '../../helpers/whitespace';
import tutorials from '../../data/tutorials.json';
import tutorials from '../../data/tutorials';
import Card from '@material-ui/core/Card';
import Button from '@material-ui/core/Button';
class Tutorial extends Component {
componentDidMount(){
componentDidMount() {
this.props.tutorialId(Number(this.props.match.params.tutorialId));
}
componentDidUpdate(props, state){
if(props.currentTutorialId !== 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(){
componentWillUnmount() {
this.props.tutorialId(null);
this.props.workspaceName(null);
}
@@ -43,30 +43,30 @@ class Tutorial extends Component {
var name = step ? `${detectWhitespacesAndReturnReadableResult(tutorial.title)}_${detectWhitespacesAndReturnReadableResult(step.headline)}` : null;
return (
!Number.isInteger(currentTutorialId) || currentTutorialId < 1 || !tutorial ?
<NotFound button={{title: 'Zurück zur Tutorials-Übersicht', link: '/tutorial'}}/>
:
<div>
<Breadcrumbs content={[{link: '/tutorial', title: 'Tutorial'}, {link: `/tutorial/${currentTutorialId}`, title: tutorial.title}]}/>
<NotFound button={{ title: 'Zurück zur Tutorials-Übersicht', link: '/tutorial' }} />
:
<div>
<Breadcrumbs content={[{ link: '/tutorial', title: 'Tutorial' }, { link: `/tutorial/${currentTutorialId}`, title: tutorial.title }]} />
<StepperHorizontal />
<StepperHorizontal />
<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} name={name}/> // if step.type === 'assessment'
: null}
<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} name={name} /> // if step.type === 'assessment'
: null}
<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 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>
</div>
);
};
}
+17 -15
View File
@@ -6,7 +6,8 @@ import clsx from 'clsx';
import Breadcrumbs from '../Breadcrumbs';
import tutorials from '../../data/tutorials.json';
import tutorials from '../../data/tutorials';
// import tutorials from '../../data/tutorials.json';
import { Link } from 'react-router-dom';
@@ -54,7 +55,7 @@ class TutorialHome extends Component {
render() {
return (
<div>
<Breadcrumbs content={[{link: '/tutorial', title: 'Tutorial'}]}/>
<Breadcrumbs content={[{ link: '/tutorial', title: 'Tutorial' }]} />
<h1>Tutorial-Übersicht</h1>
<Grid container spacing={2}>
@@ -62,36 +63,37 @@ class TutorialHome extends Component {
var status = this.props.status.filter(status => status.id === tutorial.id)[0];
var tasks = status.tasks;
var error = status.tasks.filter(task => task.type === 'error').length > 0;
var success = status.tasks.filter(task => task.type === 'success').length/tasks.length
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/${tutorial.id}`} style={{textDecoration: 'none', color: 'inherit'}}>
<Paper style={{height: '150px', padding: '10px', position:'relative', overflow: 'hidden'}}>
<Link to={`/tutorial/${tutorial.id}`} style={{ textDecoration: 'none', color: 'inherit' }}>
<Paper style={{ height: '150px', padding: '10px', position: 'relative', overflow: 'hidden' }}>
{tutorial.title}
<div className={clsx(this.props.classes.outerDiv)} style={{width: '160px', height: '160px', border: 0}}>
<svg style={{width: '100%', height: '100%'}}>
<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>}
<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 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}
: 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}/>
: <Typography variant='h3' className={success > 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success*100)}%</Typography>
<FontAwesomeIcon size='4x' icon={tutorialStatus === 'Success' ? faCheck : faTimes} />
: <Typography variant='h3' className={success > 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success * 100)}%</Typography>
}
</div>
</div>
</Paper>
</Link>
</Grid>
)})}
)
})}
</Grid>
</div>
);
@@ -108,4 +110,4 @@ const mapStateToProps = state => ({
status: state.tutorial.status
});
export default connect(mapStateToProps, null)(withStyles(styles, {withTheme: true})(TutorialHome));
export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(TutorialHome));