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" |         "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": { |     "@cnakazawa/watch": { | ||||||
|       "version": "1.0.4", |       "version": "1.0.4", | ||||||
|       "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", |       "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", |       "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", | ||||||
|       "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" |       "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": { |     "cliui": { | ||||||
|       "version": "5.0.0", |       "version": "5.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", |       "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", |       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", | ||||||
|       "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" |       "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": { |     "depd": { | ||||||
|       "version": "1.1.2", |       "version": "1.1.2", | ||||||
|       "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", |       "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": { |     "graceful-fs": { | ||||||
|       "version": "4.2.4", |       "version": "4.2.4", | ||||||
|       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", |       "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": { |     "process": { | ||||||
|       "version": "0.11.10", |       "version": "0.11.10", | ||||||
|       "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", |       "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", | ||||||
| @ -11659,6 +11703,12 @@ | |||||||
|         "ajv-keywords": "^3.4.1" |         "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": { |     "select-hose": { | ||||||
|       "version": "2.0.0", |       "version": "2.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", |       "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", |       "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", | ||||||
|       "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" |       "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": { |     "tiny-invariant": { | ||||||
|       "version": "1.1.0", |       "version": "1.1.0", | ||||||
|       "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", |       "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", | ||||||
|  | |||||||
| @ -3,6 +3,8 @@ | |||||||
|   "version": "0.1.0", |   "version": "0.1.0", | ||||||
|   "private": true, |   "private": true, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|  |     "@blockly/block-plus-minus": "^2.0.8", | ||||||
|  |     "@blockly/field-slider": "^2.0.7", | ||||||
|     "@fortawesome/fontawesome-svg-core": "^1.2.30", |     "@fortawesome/fontawesome-svg-core": "^1.2.30", | ||||||
|     "@fortawesome/free-solid-svg-icons": "^5.14.0", |     "@fortawesome/free-solid-svg-icons": "^5.14.0", | ||||||
|     "@fortawesome/react-fontawesome": "^0.1.11", |     "@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) => { | export const tutorialChange = () => (dispatch) => { | ||||||
|   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 tutorialsStatus = getState().tutorial.status; | ||||||
|   var id = getState().tutorial.currentId; |   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({ |   dispatch({ | ||||||
|     type: status === 'success' ? TUTORIAL_SUCCESS : TUTORIAL_ERROR, |     type: status === 'success' ? TUTORIAL_SUCCESS : TUTORIAL_ERROR, | ||||||
|     payload: tutorialsStatus |     payload: tutorialsStatus | ||||||
| @ -19,24 +26,25 @@ export const tutorialCheck = (status) => (dispatch, getState) => { | |||||||
| 
 | 
 | ||||||
| export const storeTutorialXml = (code) => (dispatch, getState) => { | export const storeTutorialXml = (code) => (dispatch, getState) => { | ||||||
|   var id = getState().tutorial.currentId; |   var id = getState().tutorial.currentId; | ||||||
|   var level = getState().tutorial.level; |   if(id !== null){ | ||||||
|   if(id !== null && level === 'assessment'){ |     var activeStep = getState().tutorial.activeStep; | ||||||
|     var tutorialsStatus = getState().tutorial.status; |     var steps = tutorials.filter(tutorial => tutorial.id === id)[0].steps; | ||||||
|     tutorialsStatus[id] = {...tutorialsStatus[id], xml: code}; |     if(steps[activeStep].type === 'task'){ | ||||||
|     dispatch({ |       var tutorialsStatus = getState().tutorial.status; | ||||||
|       type: TUTORIAL_XML, |       var tutorialsStatusIndex = tutorialsStatus.findIndex(tutorialStatus => tutorialStatus.id === id); | ||||||
|       payload: tutorialsStatus |       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) => { | export const tutorialId = (id) => (dispatch) => { | ||||||
|   dispatch({ |   dispatch({ | ||||||
| @ -44,3 +52,10 @@ export const tutorialId = (id) => (dispatch) => { | |||||||
|     payload: id |     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_CHANGE = 'TUTORIAL_CHANGE'; | ||||||
| export const TUTORIAL_XML = 'TUTORIAL_XML'; | export const TUTORIAL_XML = 'TUTORIAL_XML'; | ||||||
| export const TUTORIAL_ID = 'TUTORIAL_ID'; | export const TUTORIAL_ID = 'TUTORIAL_ID'; | ||||||
| export const TUTORIAL_LEVEL = 'TUTORIAL_LEVEL'; | export const TUTORIAL_STEP = 'TUTORIAL_STEP'; | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| import React, { Component } from 'react'; | import React, { Component } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import { onChangeWorkspace } from '../../actions/workspaceActions'; | import { onChangeWorkspace, clearStats } from '../../actions/workspaceActions'; | ||||||
| import * as De from './msg/de'; | import * as De from './msg/de'; | ||||||
| import BlocklyComponent from './'; | import BlocklyComponent from './'; | ||||||
| import * as Blockly from 'blockly/core'; | import * as Blockly from 'blockly/core'; | ||||||
| @ -22,23 +22,25 @@ class BlocklyWindow extends Component { | |||||||
|   componentDidMount() { |   componentDidMount() { | ||||||
|     const workspace = Blockly.getMainWorkspace(); |     const workspace = Blockly.getMainWorkspace(); | ||||||
|     this.props.onChangeWorkspace({}); |     this.props.onChangeWorkspace({}); | ||||||
|  |     this.props.clearStats(); | ||||||
|     workspace.addChangeListener((event) => { |     workspace.addChangeListener((event) => { | ||||||
|       this.props.onChangeWorkspace(event); |       this.props.onChangeWorkspace(event); | ||||||
|       Blockly.Events.disableOrphans(event); |       Blockly.Events.disableOrphans(event); | ||||||
|     }); |     }); | ||||||
|  |     Blockly.svgResize(workspace); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentDidUpdate(props) { |   componentDidUpdate(props) { | ||||||
|  |     const workspace = Blockly.getMainWorkspace(); | ||||||
|     if(props.initialXml !== this.props.initialXml){ |     if(props.initialXml !== this.props.initialXml){ | ||||||
|       // guarantees that the current xml-code (this.props.initialXml) is rendered
 |       // guarantees that the current xml-code (this.props.initialXml) is rendered
 | ||||||
|       const workspace = Blockly.getMainWorkspace(); |  | ||||||
|       workspace.clear(); |       workspace.clear(); | ||||||
|       Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(this.props.initialXml), workspace); |       Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(this.props.initialXml), workspace); | ||||||
|     } |     } | ||||||
|  |     Blockly.svgResize(workspace); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render() { |   render() { | ||||||
|     console.log(this.props.initialXml); |  | ||||||
|     return ( |     return ( | ||||||
|       <BlocklyComponent ref={this.simpleWorkspace} |       <BlocklyComponent ref={this.simpleWorkspace} | ||||||
|         style={this.props.blocklyCSS} |         style={this.props.blocklyCSS} | ||||||
| @ -76,8 +78,9 @@ class BlocklyWindow extends Component { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| BlocklyWindow.propTypes = { | 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-web'; | ||||||
| import './sensebox-display'; | import './sensebox-display'; | ||||||
| import './sensebox-lora'; | import './sensebox-lora'; | ||||||
|  | import './sensebox-led'; | ||||||
| import './io'; | import './io'; | ||||||
| import './math'; | import './math'; | ||||||
| import './map'; | import './map'; | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| import * as Blockly from 'blockly/core'; | import * as Blockly from 'blockly/core'; | ||||||
| import { getColour } from '../helpers/colour'; | import { getColour } from '../helpers/colour'; | ||||||
| import * as Types from '../helpers/types' | import * as Types from '../helpers/types' | ||||||
|  | import { FieldSlider } from '@blockly/field-slider'; | ||||||
|  | import { Field } from '..'; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Blockly.Blocks['sensebox_display_beginDisplay'] = { | Blockly.Blocks['sensebox_display_beginDisplay'] = { | ||||||
| @ -35,12 +37,15 @@ Blockly.Blocks['sensebox_display_printDisplay'] = { | |||||||
|         this.appendDummyInput() |         this.appendDummyInput() | ||||||
|             .appendField(Blockly.Msg.senseBox_display_color) |             .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"); |             .appendField(new Blockly.FieldDropdown([[Blockly.Msg.senseBox_display_white, "WHITE,BLACK"], [Blockly.Msg.senseBox_display_black, "BLACK,WHITE"]]), "COLOR"); | ||||||
|         this.appendValueInput("SIZE", 'Number') |         this.appendDummyInput() | ||||||
|             .appendField(Blockly.Msg.senseBox_display_setSize); |             .appendField(Blockly.Msg.senseBox_display_setSize) | ||||||
|         this.appendValueInput("X", 'Number') |             .appendField(new FieldSlider(1, 1, 4), "SIZE"); | ||||||
|             .appendField(Blockly.Msg.senseBox_display_printDisplay_x); |         this.appendDummyInput() | ||||||
|         this.appendValueInput("Y", 'Number') |             .appendField(Blockly.Msg.senseBox_display_printDisplay_x) | ||||||
|             .appendField(Blockly.Msg.senseBox_display_printDisplay_y); |             .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') |         this.appendValueInput('printDisplay') | ||||||
|             .appendField(Blockly.Msg.senseBox_display_printDisplay_value) |             .appendField(Blockly.Msg.senseBox_display_printDisplay_value) | ||||||
|             .setCheck(null); |             .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-web'; | ||||||
| import './sensebox-display'; | import './sensebox-display'; | ||||||
| import './sensebox-lora'; | import './sensebox-lora'; | ||||||
|  | import './sensebox-led'; | ||||||
| import './logic'; | import './logic'; | ||||||
| import './math'; | import './math'; | ||||||
| import './map'; | 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 React from 'react'; | ||||||
| import { Block, Value, Field, Shadow, Category } from '../'; | import { Block, Value, Field, Shadow, Category } from '../'; | ||||||
| import { getColour } from '../helpers/colour' | import { getColour } from '../helpers/colour' | ||||||
|  | import '@blockly/block-plus-minus'; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Toolbox extends React.Component { | class Toolbox extends React.Component { | ||||||
| @ -23,6 +24,9 @@ class Toolbox extends React.Component { | |||||||
|                         <Block type="sensebox_wifi" /> |                         <Block type="sensebox_wifi" /> | ||||||
|                         <Block type="sensebox_startap" /> |                         <Block type="sensebox_startap" /> | ||||||
|                     </Category> |                     </Category> | ||||||
|  |                     <Category name="LED" colour={getColour().sensebox}> | ||||||
|  |                         <Block type="sensebox_rgb_led" /> | ||||||
|  |                     </Category> | ||||||
|                     <Category name="Display" colour={getColour().sensebox}> |                     <Category name="Display" colour={getColour().sensebox}> | ||||||
|                         <Block type="sensebox_display_beginDisplay" /> |                         <Block type="sensebox_display_beginDisplay" /> | ||||||
|                         <Block type="sensebox_display_show" /> |                         <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 Hardware from './Hardware'; | ||||||
| import BlocklyWindow from '../Blockly/BlocklyWindow'; | import BlocklyWindow from '../Blockly/BlocklyWindow'; | ||||||
| 
 | 
 | ||||||
| import { tutorials } from './tutorials'; |  | ||||||
| 
 |  | ||||||
| import Grid from '@material-ui/core/Grid'; | import Grid from '@material-ui/core/Grid'; | ||||||
| import Typography from '@material-ui/core/Typography'; | import Typography from '@material-ui/core/Typography'; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,13 +1,14 @@ | |||||||
| import React, { Component } from 'react'; | import React, { Component } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import { 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 Compile from '../Compile'; | ||||||
| 
 | 
 | ||||||
| import { tutorials } from './tutorials'; | import tutorials from './tutorials.json'; | ||||||
|  | import { checkXml } from './compareXml'; | ||||||
| 
 | 
 | ||||||
| import { withStyles } from '@material-ui/core/styles'; | import { withStyles } from '@material-ui/core/styles'; | ||||||
| import IconButton from '@material-ui/core/IconButton'; | import IconButton from '@material-ui/core/IconButton'; | ||||||
| @ -48,62 +49,79 @@ class SolutionCheck extends Component { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   check = () => { |   check = () => { | ||||||
|     const workspace = Blockly.getMainWorkspace(); |     const tutorial = tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0]; | ||||||
|     var msg = tutorials[this.props.currentTutorialId].test(workspace); |     const step = tutorial.steps[this.props.activeStep]; | ||||||
|     this.props.tutorialCheck(msg.type); |     var msg = checkXml(step.xml, this.props.xml); | ||||||
|  |     this.props.tutorialCheck(msg.type, step); | ||||||
|     this.setState({ msg, open: true }); |     this.setState({ msg, open: true }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render() { |   render() { | ||||||
|  |     const steps = tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0].steps; | ||||||
|     return ( |     return ( | ||||||
|       tutorials[this.props.currentTutorialId].test ? |       <div> | ||||||
|         <div> |         <Tooltip title='Lösung kontrollieren'> | ||||||
|           <Tooltip title='Lösung kontrollieren'> |           <IconButton | ||||||
|             <IconButton |             className={this.props.classes.compile} | ||||||
|               className={this.props.classes.compile} |             style={{width: '40px', height: '40px', position: 'absolute', top: 8, right: 8, zIndex: 21 }} | ||||||
|               style={{width: '40px', height: '40px', position: 'absolute', top: 8, right: 8, zIndex: 21 }} |             onClick={() => this.check()} | ||||||
|               onClick={() => this.check()} |           > | ||||||
|             > |             <FontAwesomeIcon icon={faPlay} size="xs"/> | ||||||
|               <FontAwesomeIcon icon={faPlay} size="xs"/> |           </IconButton> | ||||||
|             </IconButton> |         </Tooltip> | ||||||
|           </Tooltip> |         <Dialog fullWidth maxWidth={'sm'} onClose={this.toggleDialog} open={this.state.open} style={{zIndex: 9999999}}> | ||||||
|           <Dialog fullWidth maxWidth={'sm'} onClose={this.toggleDialog} open={this.state.open} style={{zIndex: 9999999}}> |           <DialogTitle>{this.state.msg.type === 'error' ? 'Fehler' : 'Erfolg'}</DialogTitle> | ||||||
|             <DialogTitle>{this.state.msg.type === 'error' ? 'Fehler' : 'Erfolg'}</DialogTitle> |           <DialogContent dividers> | ||||||
|             <DialogContent dividers> |             {this.state.msg.text} | ||||||
|               {this.state.msg.text} |             {this.state.msg.type === 'success' ? | ||||||
|               {this.state.msg.type === 'success' ? |  | ||||||
|               <div style={{marginTop: '20px', display: 'flex'}}> |               <div style={{marginTop: '20px', display: 'flex'}}> | ||||||
|                 <Compile /> |                 <Compile /> | ||||||
|                 <Button |                 {this.props.activeStep === steps.length-1 ? | ||||||
|                   style={{marginLeft: '10px'}} |                   <Button | ||||||
|                   variant="contained" |                     style={{marginLeft: '10px'}} | ||||||
|                   color="primary" |                     variant="contained" | ||||||
|                   onClick={() => {this.toggleDialog(); this.props.history.push(`/tutorial/${this.props.currentTutorialId+2}`)}} |                     color="primary" | ||||||
|                 > |                     onClick={() => {this.toggleDialog(); this.props.history.push(`/tutorial/`)}} | ||||||
|                   nächstes Tutorial |                   > | ||||||
|                 </Button> |                     Tutorials-Übersicht | ||||||
|                 </div> |                   </Button> | ||||||
|               : null} |                 : | ||||||
|             </DialogContent> |                   <Button | ||||||
|             <DialogActions> |                     style={{marginLeft: '10px'}} | ||||||
|               <Button onClick={this.toggleDialog} color="primary"> |                     variant="contained" | ||||||
|                 Schließen |                     color="primary" | ||||||
|               </Button> |                     onClick={() => {this.toggleDialog(); this.props.tutorialStep(this.props.activeStep + 1)}} | ||||||
|             </DialogActions> |                   > | ||||||
|           </Dialog> |                     nächster Schritt | ||||||
|         </div> |                   </Button> | ||||||
|       : null |                 } | ||||||
|  |               </div> | ||||||
|  |             : null} | ||||||
|  |           </DialogContent> | ||||||
|  |           <DialogActions> | ||||||
|  |             <Button onClick={this.toggleDialog} color="primary"> | ||||||
|  |               Schließen | ||||||
|  |             </Button> | ||||||
|  |           </DialogActions> | ||||||
|  |         </Dialog> | ||||||
|  |       </div> | ||||||
|     ); |     ); | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| SolutionCheck.propTypes = { | SolutionCheck.propTypes = { | ||||||
|   tutorialCheck: PropTypes.func.isRequired, |   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 => ({ | 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 clsx from 'clsx'; | ||||||
| 
 | 
 | ||||||
| import { tutorials } from './tutorials'; | import tutorials from './tutorials.json'; | ||||||
| 
 | 
 | ||||||
| import { fade } from '@material-ui/core/styles/colorManipulator'; | import { fade } from '@material-ui/core/styles/colorManipulator'; | ||||||
| import { withStyles } from '@material-ui/core/styles'; | import { withStyles } from '@material-ui/core/styles'; | ||||||
| @ -21,6 +21,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | |||||||
| const styles = (theme) => ({ | const styles = (theme) => ({ | ||||||
|   stepper: { |   stepper: { | ||||||
|     width: 'calc(100% - 40px)', |     width: 'calc(100% - 40px)', | ||||||
|  |     height: '40px', | ||||||
|     borderRadius: '25px', |     borderRadius: '25px', | ||||||
|     padding: '0 20px', |     padding: '0 20px', | ||||||
|     margin: '20px 0', |     margin: '20px 0', | ||||||
| @ -51,30 +52,43 @@ class StepperHorizontal extends Component { | |||||||
| 
 | 
 | ||||||
|   render() { |   render() { | ||||||
|     var tutorialId = this.props.currentTutorialId; |     var tutorialId = this.props.currentTutorialId; | ||||||
|     var tutorialStatus = this.props.status[tutorialId].status === 'success' ? 'Success' : |     var status = this.props.status.filter(status => status.id === tutorialId)[0]; | ||||||
|                           this.props.status[tutorialId].status === 'error' ? 'Error' : 'Other'; |     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 ( |     return ( | ||||||
|       <div className={clsx(this.props.classes.stepper, this.props.classes['stepper'+tutorialStatus])}> |       <div style={{position: 'relative'}}> | ||||||
|         <Button |         {error || success > 0 ? | ||||||
|           disabled={tutorialId === 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)}> | ||||||
|           onClick={() => {this.props.history.push(`/tutorial/${tutorialId}`)}} |           </div> | ||||||
|         > |         : null} | ||||||
|           {'<'} |         {success < 1 && !error ? | ||||||
|         </Button> |           <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)}> | ||||||
|         <Stepper activeStep={tutorialId+1} orientation="horizontal" |           </div> | ||||||
|                  style={{padding: 0}} classes={{root: this.props.classes.color}}> |         : null} | ||||||
|           <Step expanded completed={false}> |         <div className={this.props.classes.stepper}> | ||||||
|             <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> : ''}> |           <Button | ||||||
|               <h1 style={{margin: 0}}>{tutorials[tutorialId].title}</h1> |             disabled={tutorialId === 1} | ||||||
|             </StepLabel> |             onClick={() => {this.props.history.push(`/tutorial/${tutorialId-1}`)}} | ||||||
|           </Step> |           > | ||||||
|         </Stepper> |             {'<'} | ||||||
|         <Button |           </Button> | ||||||
|           disabled={tutorialId+2 > tutorials.length} |           <Stepper activeStep={tutorialId} orientation="horizontal" | ||||||
|           onClick={() => {this.props.history.push(`/tutorial/${tutorialId+2}`)}} |                    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> : ''}> | ||||||
|         </Button> |                 <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> |       </div> | ||||||
|     ); |     ); | ||||||
|   }; |   }; | ||||||
|  | |||||||
| @ -1,17 +1,14 @@ | |||||||
| import React, { Component } from 'react'; | import React, { Component } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
|  | import { tutorialStep } from '../../actions/tutorialActions'; | ||||||
| 
 | 
 | ||||||
| import { withRouter, Link } from 'react-router-dom'; | import { withRouter } from 'react-router-dom'; | ||||||
| 
 | 
 | ||||||
| import clsx from 'clsx'; | import clsx from 'clsx'; | ||||||
| 
 | 
 | ||||||
| import { tutorials } from './tutorials'; |  | ||||||
| 
 |  | ||||||
| import { fade } from '@material-ui/core/styles/colorManipulator'; | import { fade } from '@material-ui/core/styles/colorManipulator'; | ||||||
| import { withStyles } from '@material-ui/core/styles'; | import { withStyles } from '@material-ui/core/styles'; | ||||||
| import withWidth, { isWidthUp } from '@material-ui/core/withWidth'; |  | ||||||
| import Button from '@material-ui/core/Button'; |  | ||||||
| import Stepper from '@material-ui/core/Stepper'; | import Stepper from '@material-ui/core/Stepper'; | ||||||
| import Step from '@material-ui/core/Step'; | import Step from '@material-ui/core/Step'; | ||||||
| import StepLabel from '@material-ui/core/StepLabel'; | import StepLabel from '@material-ui/core/StepLabel'; | ||||||
| @ -26,30 +23,23 @@ const styles = (theme) => ({ | |||||||
|     borderStyle: `solid`, |     borderStyle: `solid`, | ||||||
|     // borderWidth: '2px',
 |     // borderWidth: '2px',
 | ||||||
|     borderRadius: '50%', |     borderRadius: '50%', | ||||||
|  |     borderColor: theme.palette.secondary.main, | ||||||
|     width: '12px', |     width: '12px', | ||||||
|     height: '12px', |     height: '12px', | ||||||
|     margin: '0 auto' |     margin: '0 auto', | ||||||
|   }, |  | ||||||
|   stepIconMedium: { |  | ||||||
|     width: '18px', |  | ||||||
|     height: '18px', |  | ||||||
|   }, |   }, | ||||||
|   stepIconLarge: { |   stepIconLarge: { | ||||||
|     width: '24px', |     width: '24px', | ||||||
|     height: '24px' |     height: '24px' | ||||||
|   }, |   }, | ||||||
|   stepIconTransparent: { |   stepIconLargeSuccess: { | ||||||
|     borderColor: `transparent`, |  | ||||||
|     cursor: 'default' |  | ||||||
|   }, |  | ||||||
|   stepIconSuccess: { |  | ||||||
|     borderColor: theme.palette.primary.main, |     borderColor: theme.palette.primary.main, | ||||||
|   }, |   }, | ||||||
|   stepIconError: { |   stepIconLargeError: { | ||||||
|     borderColor: theme.palette.error.dark, |     borderColor: theme.palette.error.dark, | ||||||
|   }, |   }, | ||||||
|   stepIconOther: { |   stepIconActiveOther: { | ||||||
|     borderColor: theme.palette.secondary.main, |     backgroundColor: theme.palette.secondary.main | ||||||
|   }, |   }, | ||||||
|   stepIconActiveSuccess: { |   stepIconActiveSuccess: { | ||||||
|     backgroundColor: fade(theme.palette.primary.main, 0.6) |     backgroundColor: fade(theme.palette.primary.main, 0.6) | ||||||
| @ -57,145 +47,64 @@ const styles = (theme) => ({ | |||||||
|   stepIconActiveError: { |   stepIconActiveError: { | ||||||
|     backgroundColor: fade(theme.palette.error.dark, 0.6) |     backgroundColor: fade(theme.palette.error.dark, 0.6) | ||||||
|   }, |   }, | ||||||
|   stepIconActiveOther: { |   connector: { | ||||||
|     backgroundColor: fade(theme.palette.secondary.main, 0.6) |     height: '10px', | ||||||
|   }, |     borderLeft: `2px solid black`, | ||||||
|   progress: { |     margin: 'auto' | ||||||
|     position: 'absolute', |  | ||||||
|     top: 0, |  | ||||||
|     right: 0, |  | ||||||
|     marginRight: '5px', |  | ||||||
|     width: '3px', |  | ||||||
|   }, |  | ||||||
|   progressForeground: { |  | ||||||
|     backgroundColor: theme.palette.primary.main |  | ||||||
|   }, |  | ||||||
|   progressBackground: { |  | ||||||
|     backgroundColor: fade(theme.palette.primary.main, 0.2), |  | ||||||
|     height: '100%', |  | ||||||
|     borderRadius: '2px' |  | ||||||
|   } |   } | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| class StepperVertical extends Component { | class StepperVertical extends Component { | ||||||
| 
 | 
 | ||||||
|   constructor(props){ |   componentDidMount(){ | ||||||
|     super(props); |     this.props.tutorialStep(0); | ||||||
|     this.state = { |  | ||||||
|       tutorialArray: props.currentTutorialId === 0 ? |  | ||||||
|                       tutorials.slice(props.currentTutorialId, props.currentTutorialId+5) |  | ||||||
|                       : props.currentTutorialId === 1 ? |  | ||||||
|                           tutorials.slice(props.currentTutorialId-1, props.currentTutorialId+4) |  | ||||||
|                           : props.currentTutorialId === tutorials.length-1 ? |  | ||||||
|                                 tutorials.slice(props.currentTutorialId-4, props.currentTutorialId+5) |  | ||||||
|                               : props.currentTutorialId === tutorials.length-2 ? |  | ||||||
|                                     tutorials.slice(props.currentTutorialId-3, props.currentTutorialId+4) |  | ||||||
|                                   : tutorials.slice(props.currentTutorialId-2, props.currentTutorialId+3), |  | ||||||
|       selectedVerticalTutorialId: props.currentTutorialId |  | ||||||
|     }; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentDidUpdate(props){ |   componentDidUpdate(props){ | ||||||
|     if(props.currentTutorialId !== this.props.currentTutorialId){ |     if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){ | ||||||
|       this.setState({ |       this.props.tutorialStep(0); | ||||||
|         tutorialArray: this.props.currentTutorialId === 0 ? |  | ||||||
|                         tutorials.slice(this.props.currentTutorialId, this.props.currentTutorialId+5) |  | ||||||
|                         : this.props.currentTutorialId === 1 ? |  | ||||||
|                             tutorials.slice(this.props.currentTutorialId-1, this.props.currentTutorialId+4) |  | ||||||
|                             : this.props.currentTutorialId === tutorials.length-1 ? |  | ||||||
|                                   tutorials.slice(this.props.currentTutorialId-4, this.props.currentTutorialId+5) |  | ||||||
|                                 : this.props.currentTutorialId === tutorials.length-2 ? |  | ||||||
|                                       tutorials.slice(this.props.currentTutorialId-3, this.props.currentTutorialId+4) |  | ||||||
|                                     : tutorials.slice(this.props.currentTutorialId-2, this.props.currentTutorialId+3), |  | ||||||
|         selectedVerticalTutorialId: this.props.currentTutorialId |  | ||||||
|       }); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   verticalStepper = (step) => { |  | ||||||
|     var newTutorialId = this.state.selectedVerticalTutorialId + step; |  | ||||||
|     var tutorialArray = newTutorialId === 0 ? |  | ||||||
|                           tutorials.slice(newTutorialId, newTutorialId+5) |  | ||||||
|                           : newTutorialId === 1 ? |  | ||||||
|                                 tutorials.slice(newTutorialId-1, newTutorialId+4) |  | ||||||
|                               : newTutorialId === tutorials.length-1 ? |  | ||||||
|                                     tutorials.slice(newTutorialId-4, newTutorialId+5) |  | ||||||
|                                   : newTutorialId === tutorials.length-2 ? |  | ||||||
|                                         tutorials.slice(newTutorialId-3, newTutorialId+4) |  | ||||||
|                                       : tutorials.slice(newTutorialId-2, newTutorialId+3); |  | ||||||
|     this.setState({ tutorialArray: tutorialArray, selectedVerticalTutorialId: newTutorialId }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   render() { |   render() { | ||||||
|     var tutorialId = this.props.currentTutorialId; |     var steps = this.props.steps; | ||||||
|     var selectedVerticalTutorialId = this.state.selectedVerticalTutorialId; |     var activeStep = this.props.activeStep; | ||||||
|  |     var tutorialStatus = this.props.status.filter(status => status.id === this.props.currentTutorialId)[0]; | ||||||
|     return ( |     return ( | ||||||
|       isWidthUp('sm', this.props.width) ? |       <div style={{marginRight: '10px'}}> | ||||||
|         <div style={{marginRight: '10px'}}> |         <Stepper | ||||||
|             <Button |           activeStep={activeStep} | ||||||
|               style={{minWidth: '30px', margin: 'auto', minHeight: '25px', padding: '0', writingMode: 'vertical-rl'}} |           orientation="vertical" | ||||||
|               disabled={selectedVerticalTutorialId === 0} |           connector={<div className={this.props.classes.connector}></div>} | ||||||
|               onClick={() => {this.verticalStepper(-1)}} |           classes={{root: this.props.classes.verticalStepper}} | ||||||
|             > |         > | ||||||
|               {'<'} |           {steps.map((step, i) => { | ||||||
|             </Button> |             var tasksIndex = tutorialStatus.tasks.findIndex(task => task.id === step.id); | ||||||
|             <div style={{display: 'flex', height: 'max-content', width: 'max-content'}}> |             var taskType = tasksIndex > -1 ? tutorialStatus.tasks[tasksIndex].type : null; | ||||||
|               <div style={{position: 'relative'}}> |             var taskStatus = taskType === 'success' ? 'Success' : taskType === 'error' ? 'Error' : 'Other'; | ||||||
|                 <div |             return ( | ||||||
|                   className={clsx(this.props.classes.progress, this.props.classes.progressForeground)} |               <Step key={i}> | ||||||
|                   style={{ zIndex: 1, borderRadius: `${selectedVerticalTutorialId/(tutorials.length-1) === 1 ? '2px' : '2px 2px 0 0'}`, height: `${((selectedVerticalTutorialId+1)/tutorials.length)*100}%`}}> |                 <Tooltip title={step.headline} placement='right' arrow > | ||||||
|                 </div> |                   <div style={i === activeStep ? {padding: '5px 0'} : {padding: '5px 0', cursor: 'pointer'}} onClick={i === activeStep ? null : () => {this.props.tutorialStep(i)}}> | ||||||
|                 <div className={clsx(this.props.classes.progress, this.props.classes.progressBackground)}> |                     <StepLabel | ||||||
|                 </div> |                       StepIconComponent={'div'} | ||||||
|               </div> |                       classes={{ | ||||||
|               <Stepper |                         root: step.type === 'task' ? | ||||||
|                 activeStep={tutorialId+1} |                                 i === activeStep ? | ||||||
|                 orientation="vertical" |                                   clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIconLarge'+taskStatus], this.props.classes['stepIconActive'+taskStatus]) | ||||||
|                 connector={<div style={{height: '10px'}}></div>} |                                 : clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIconLarge'+taskStatus]) | ||||||
|                 classes={{root: this.props.classes.verticalStepper}} |                               : i === activeStep ? | ||||||
|               > |                                   clsx(this.props.classes.stepIcon, this.props.classes.stepIconActiveOther) | ||||||
|                 {this.state.tutorialArray.map((tutorial, i) => { |                                 : clsx(this.props.classes.stepIcon) | ||||||
|                   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' : |                     </StepLabel> | ||||||
|                                         this.props.status[verticalTutorialId-1].status === 'error' ? 'Error' : 'Other'; |                   </div> | ||||||
|                   return ( |                 </Tooltip> | ||||||
|                     <Step key={i}> |               </Step> | ||||||
|                       <Tooltip title={Object.keys(tutorial).length > 0 ? tutorial.title : ''} placement='right' arrow > |           )})} | ||||||
|                         <Link to={`/tutorial/${verticalTutorialId}`}> |         </Stepper> | ||||||
|                           <StepLabel |       </div> | ||||||
|                             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 |  | ||||||
|     ); |     ); | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| @ -204,13 +113,16 @@ class StepperVertical extends Component { | |||||||
| StepperVertical.propTypes = { | StepperVertical.propTypes = { | ||||||
|   status: PropTypes.array.isRequired, |   status: PropTypes.array.isRequired, | ||||||
|   change: PropTypes.number.isRequired, |   change: PropTypes.number.isRequired, | ||||||
|   currentTutorialId: PropTypes.number.isRequired |   currentTutorialId: PropTypes.number.isRequired, | ||||||
|  |   activeStep: PropTypes.number.isRequired, | ||||||
|  |   tutorialStep: PropTypes.func.isRequired | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||||
|   change: state.tutorial.change, |   change: state.tutorial.change, | ||||||
|   status: state.tutorial.status, |   status: state.tutorial.status, | ||||||
|   currentTutorialId: state.tutorial.currentId |   currentTutorialId: state.tutorial.currentId, | ||||||
|  |   activeStep: state.tutorial.activeStep | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, null)(withRouter(withStyles(styles, {withTheme: true})(withWidth()(StepperVertical)))); | export default connect(mapStateToProps, { tutorialStep })(withRouter(withStyles(styles, {withTheme: true})(StepperVertical))); | ||||||
|  | |||||||
| @ -1,35 +1,29 @@ | |||||||
| import React, { Component } from 'react'; | import React, { Component } from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import { tutorialId, setTutorialLevel } from '../../actions/tutorialActions'; | import { tutorialId, tutorialStep } from '../../actions/tutorialActions'; | ||||||
| 
 | 
 | ||||||
| import Breadcrumbs from '../Breadcrumbs'; | import Breadcrumbs from '../Breadcrumbs'; | ||||||
| import StepperHorizontal from './StepperHorizontal'; | import StepperHorizontal from './StepperHorizontal'; | ||||||
| import StepperVertical from './StepperVertical'; | import StepperVertical from './StepperVertical'; | ||||||
| import Instruction from './Instruction'; | import Instruction from './Instruction'; | ||||||
| import BlocklyWindow from '../Blockly/BlocklyWindow'; | import Assessment from './Assessment'; | ||||||
| import SolutionCheck from './SolutionCheck'; |  | ||||||
| import CodeViewer from '../CodeViewer'; |  | ||||||
| import NotFound from '../NotFound'; | import NotFound from '../NotFound'; | ||||||
| 
 | 
 | ||||||
| import { tutorials } from './tutorials'; | import tutorials from './tutorials.json'; | ||||||
| 
 | 
 | ||||||
| import withWidth, { isWidthUp } from '@material-ui/core/withWidth'; |  | ||||||
| import Tabs from '@material-ui/core/Tabs'; |  | ||||||
| import Tab from '@material-ui/core/Tab'; |  | ||||||
| import Grid from '@material-ui/core/Grid'; |  | ||||||
| import Card from '@material-ui/core/Card'; | import Card from '@material-ui/core/Card'; | ||||||
|  | import Button from '@material-ui/core/Button'; | ||||||
| 
 | 
 | ||||||
| class Tutorial extends Component { | class Tutorial extends Component { | ||||||
| 
 | 
 | ||||||
|   componentDidMount(){ |   componentDidMount(){ | ||||||
|     this.props.tutorialId(Number(this.props.match.params.tutorialId)-1); |     this.props.tutorialId(Number(this.props.match.params.tutorialId)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentDidUpdate(props, state){ |   componentDidUpdate(props, state){ | ||||||
|     if(props.currentTutorialId+1 !== Number(this.props.match.params.tutorialId)){ |     if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){ | ||||||
|       this.props.tutorialId(Number(this.props.match.params.tutorialId)-1); |       this.props.tutorialId(Number(this.props.match.params.tutorialId)); | ||||||
|       this.props.setTutorialLevel('instruction'); |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -37,79 +31,55 @@ class Tutorial extends Component { | |||||||
|     this.props.tutorialId(null); |     this.props.tutorialId(null); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   onChange = (e, value) => { |  | ||||||
|     this.props.setTutorialLevel(value); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   render() { |   render() { | ||||||
|     var currentTutorialId = this.props.currentTutorialId; |     var currentTutorialId = this.props.currentTutorialId; | ||||||
|     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 ( |     return ( | ||||||
|       !Number.isInteger(currentTutorialId) || currentTutorialId+1 < 1 || currentTutorialId+1 > tutorials.length ? |       !Number.isInteger(currentTutorialId) || currentTutorialId < 1 || currentTutorialId > tutorials.length ? | ||||||
|         <NotFound button={{title: 'Zurück zur Tutorials-Übersicht', link: '/tutorial'}}/> |         <NotFound button={{title: 'Zurück zur Tutorials-Übersicht', link: '/tutorial'}}/> | ||||||
|       : |       : | ||||||
|         <div> |       <div> | ||||||
|           <Breadcrumbs content={[{link: '/', title: 'Home'},{link: '/tutorial', title: 'Tutorial'}, {link: `/tutorial/${currentTutorialId+1}`, title: tutorials[currentTutorialId].title}]}/> |         <Breadcrumbs content={[{link: '/', title: 'Home'},{link: '/tutorial', title: 'Tutorial'}, {link: `/tutorial/${currentTutorialId}`, title: tutorial.title}]}/> | ||||||
| 
 | 
 | ||||||
|           <StepperHorizontal /> |         <StepperHorizontal /> | ||||||
| 
 | 
 | ||||||
|           <div style={{display: 'flex'}}> |         <div style={{display: 'flex'}}> | ||||||
|             <StepperVertical /> |           <StepperVertical steps={steps}/> | ||||||
|  |                                   {/* 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*/} |             <div style={{marginTop: '20px', position: 'absolute', bottom: '10px'}}> | ||||||
|             <Card style={{width: isWidthUp('sm', this.props.width) ? 'calc(100% - 30px)' : '100%', padding: '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> | ||||||
|               <Tabs |               <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> | ||||||
|                 value={this.props.level} |             </div> | ||||||
|                 indicatorColor="primary" |           </Card> | ||||||
|                 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> |         </div> | ||||||
|  |       </div> | ||||||
|     ); |     ); | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Tutorial.propTypes = { | Tutorial.propTypes = { | ||||||
|   tutorialId: PropTypes.func.isRequired, |   tutorialId: PropTypes.func.isRequired, | ||||||
|   setTutorialLevel: PropTypes.func.isRequired, |   tutorialStep: PropTypes.func.isRequired, | ||||||
|   currentTutorialId: PropTypes.number, |   currentTutorialId: PropTypes.number, | ||||||
|   status: PropTypes.array.isRequired, |   status: PropTypes.array.isRequired, | ||||||
|   change: PropTypes.number.isRequired, |   change: PropTypes.number.isRequired, | ||||||
|   level: PropTypes.string.isRequired |   activeStep: PropTypes.number.isRequired | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||||
|   change: state.tutorial.change, |   change: state.tutorial.change, | ||||||
|   status: state.tutorial.status, |   status: state.tutorial.status, | ||||||
|   currentTutorialId: state.tutorial.currentId, |   currentTutorialId: state.tutorial.currentId, | ||||||
|   level: state.tutorial.level |   activeStep: state.tutorial.activeStep | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default connect(mapStateToProps, { tutorialId, setTutorialLevel })(withWidth()(Tutorial)); | export default connect(mapStateToProps, { tutorialId, tutorialStep })(Tutorial); | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ import clsx from 'clsx'; | |||||||
| 
 | 
 | ||||||
| import Breadcrumbs from '../Breadcrumbs'; | import Breadcrumbs from '../Breadcrumbs'; | ||||||
| 
 | 
 | ||||||
| import { tutorials } from './tutorials'; | import tutorials from './tutorials.json'; | ||||||
| 
 | 
 | ||||||
| import { Link } from 'react-router-dom'; | import { Link } from 'react-router-dom'; | ||||||
| 
 | 
 | ||||||
| @ -14,6 +14,7 @@ import { fade } from '@material-ui/core/styles/colorManipulator'; | |||||||
| import { withStyles } from '@material-ui/core/styles'; | import { withStyles } from '@material-ui/core/styles'; | ||||||
| import Grid from '@material-ui/core/Grid'; | import Grid from '@material-ui/core/Grid'; | ||||||
| import Paper from '@material-ui/core/Paper'; | import Paper from '@material-ui/core/Paper'; | ||||||
|  | import Typography from '@material-ui/core/Typography'; | ||||||
| 
 | 
 | ||||||
| import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons"; | import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons"; | ||||||
| import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||
| @ -21,20 +22,23 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | |||||||
| const styles = (theme) => ({ | const styles = (theme) => ({ | ||||||
|   outerDiv: { |   outerDiv: { | ||||||
|     position: 'absolute', |     position: 'absolute', | ||||||
|     right: '-29px', |     right: '-30px', | ||||||
|     bottom: '-29px', |     bottom: '-30px', | ||||||
|     width: '140px', |     width: '160px', | ||||||
|     height: '140px', |     height: '160px', | ||||||
|     borderStyle: 'solid', |     color: fade(theme.palette.secondary.main, 0.6) | ||||||
|     borderWidth: '10px', |  | ||||||
|     borderRadius: '50%', |  | ||||||
|     borderColor: fade(theme.palette.primary.main, 0.2), |  | ||||||
|     color: fade(theme.palette.primary.main, 0.2) |  | ||||||
|   }, |   }, | ||||||
|   outerDivError: { |   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) |     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: { |   innerDiv: { | ||||||
|     width: 'inherit', |     width: 'inherit', | ||||||
|     height: 'inherit', |     height: 'inherit', | ||||||
| @ -55,24 +59,38 @@ class TutorialHome extends Component { | |||||||
|         <h1>Tutorial-Übersicht</h1> |         <h1>Tutorial-Übersicht</h1> | ||||||
|         <Grid container spacing={2}> |         <Grid container spacing={2}> | ||||||
|           {tutorials.map((tutorial, i) => { |           {tutorials.map((tutorial, i) => { | ||||||
|             var tutorialStatus = this.props.status[i].status === 'success' ? 'Success' : |             var steps = tutorial.steps; | ||||||
|                                   this.props.status[i].status === 'error' ? 'Error' : 'Other'; |             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 ( |             return ( | ||||||
|               <Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}> |               <Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}> | ||||||
|                 <Link to={`/tutorial/${i+1}`} style={{textDecoration: 'none', color: 'inherit'}}> |                 <Link to={`/tutorial/${tutorial.id}`} style={{textDecoration: 'none', color: 'inherit'}}> | ||||||
|                   <Paper style={{height: '150px', padding: '10px', position:'relative', overflow: 'hidden'}}> |                   <Paper style={{height: '150px', padding: '10px', position:'relative', overflow: 'hidden'}}> | ||||||
|                     {tutorials[i].title} |                     {tutorial.title} | ||||||
|                     {tutorialStatus !== 'Other' ? |                     <div className={clsx(this.props.classes.outerDiv)} style={{width: '160px', height: '160px', border: 0}}> | ||||||
|                       <div className={clsx(this.props.classes.outerDiv, tutorialStatus === 'Error' ? this.props.classes.outerDivError : null)}> |                       <svg style={{width: '100%', height: '100%'}}> | ||||||
|                         <div className={this.props.classes.innerDiv}> |                         {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}/> |                           <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> |                       </div> | ||||||
|                       : null |                     </div> | ||||||
|                     } |  | ||||||
|                   </Paper> |                   </Paper> | ||||||
|                 </Link> |                 </Link> | ||||||
| 
 |  | ||||||
|               </Grid> |               </Grid> | ||||||
|           )})} |           )})} | ||||||
|         </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> |                   </block> | ||||||
|                 </xml>` |                 </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){ |     "test": function(workspace){ | ||||||
|               var wifi = workspace.getBlocksByType('sensebox_wifi'); // result is an array with Blocks as objects
 |               var wifi = workspace.getBlocksByType('sensebox_wifi'); // result is an array with Blocks as objects
 | ||||||
|               if(wifi.length > 0){ |               if(wifi.length > 0){ | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ class WorkspaceStats extends Component { | |||||||
|             style={{ marginRight: '1rem' }} |             style={{ marginRight: '1rem' }} | ||||||
|             color="primary" |             color="primary" | ||||||
|             avatar={<Avatar><FontAwesomeIcon icon={faPlus} /></Avatar>} |             avatar={<Avatar><FontAwesomeIcon icon={faPlus} /></Avatar>} | ||||||
|             label={this.props.create > 0 ? this.props.create : 0}> // initialXML is created automatically, Block is not part of the statistics
 |             label={this.props.create > 0 ? this.props.create : 0}> {/* initialXML is created automatically, Block is not part of the statistics */} | ||||||
|           </Chip> |           </Chip> | ||||||
|         </Tooltip> |         </Tooltip> | ||||||
|         <Tooltip title="Anzahl veränderter Blöcke" > |         <Tooltip title="Anzahl veränderter Blöcke" > | ||||||
| @ -58,7 +58,7 @@ class WorkspaceStats extends Component { | |||||||
|             style={{ marginRight: '1rem' }} |             style={{ marginRight: '1rem' }} | ||||||
|             color="primary" |             color="primary" | ||||||
|             avatar={<Avatar><FontAwesomeIcon icon={faArrowsAlt} /></Avatar>} |             avatar={<Avatar><FontAwesomeIcon icon={faArrowsAlt} /></Avatar>} | ||||||
|             label={this.props.move > 0 ? this.props.move : 0}> // initialXML is moved automatically, Block is not part of the statistics
 |             label={this.props.move > 0 ? this.props.move : 0}> {/* initialXML is moved automatically, Block is not part of the statistics */} | ||||||
|           </Chip> |           </Chip> | ||||||
|         </Tooltip> |         </Tooltip> | ||||||
|         <Tooltip title="Anzahl gelöschter Blöcke" > |         <Tooltip title="Anzahl gelöschter Blöcke" > | ||||||
|  | |||||||
| @ -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 = { | const initialState = { | ||||||
|   status: window.localStorage.getItem('tutorial') ? |   status: initialStatus(), | ||||||
|             JSON.parse(window.localStorage.getItem('tutorial')) |  | ||||||
|           : new Array(tutorials.length).fill({}), |  | ||||||
|   level: 'instruction', |  | ||||||
|   currentId: null, |   currentId: null, | ||||||
|  |   activeStep: 0, | ||||||
|   change: 0 |   change: 0 | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -17,7 +52,7 @@ export default function(state = initialState, action){ | |||||||
|     case TUTORIAL_ERROR: |     case TUTORIAL_ERROR: | ||||||
|     case TUTORIAL_XML: |     case TUTORIAL_XML: | ||||||
|       // update locale storage - sync with redux store
 |       // update locale storage - sync with redux store
 | ||||||
|       window.localStorage.setItem('tutorial', JSON.stringify(action.payload)); |       window.localStorage.setItem('status', JSON.stringify(action.payload)); | ||||||
|       return { |       return { | ||||||
|         ...state, |         ...state, | ||||||
|         status: action.payload |         status: action.payload | ||||||
| @ -32,10 +67,10 @@ export default function(state = initialState, action){ | |||||||
|         ...state, |         ...state, | ||||||
|         currentId: action.payload |         currentId: action.payload | ||||||
|       } |       } | ||||||
|     case TUTORIAL_LEVEL: |     case TUTORIAL_STEP: | ||||||
|       return { |       return { | ||||||
|         ...state, |         ...state, | ||||||
|         level: action.payload |         activeStep: action.payload | ||||||
|       } |       } | ||||||
|     default: |     default: | ||||||
|       return state; |       return state; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user