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