From 78f8bad4b3fedc942b67e3c48e0307f520efdabb Mon Sep 17 00:00:00 2001 From: Mario Date: Wed, 9 Sep 2020 11:54:30 +0200 Subject: [PATCH 01/10] add slider --- package-lock.json | 56 +++++++++++++++++++ package.json | 2 + src/components/Blockly/blocks/index.js | 1 + .../Blockly/blocks/sensebox-display.js | 17 ++++-- src/components/Blockly/blocks/sensebox-led.js | 28 ++++++++++ src/components/Blockly/generator/index.js | 1 + .../Blockly/generator/sensebox-led.js | 15 +++++ src/components/Blockly/toolbox/Toolbox.js | 4 ++ 8 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 src/components/Blockly/blocks/sensebox-led.js create mode 100644 src/components/Blockly/generator/sensebox-led.js diff --git a/package-lock.json b/package-lock.json index 6fd2bef..c24c9e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 6a3180b..ff3634c 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/Blockly/blocks/index.js b/src/components/Blockly/blocks/index.js index b35391f..40770dd 100644 --- a/src/components/Blockly/blocks/index.js +++ b/src/components/Blockly/blocks/index.js @@ -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'; diff --git a/src/components/Blockly/blocks/sensebox-display.js b/src/components/Blockly/blocks/sensebox-display.js index 2f163b5..795f295 100644 --- a/src/components/Blockly/blocks/sensebox-display.js +++ b/src/components/Blockly/blocks/sensebox-display.js @@ -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); diff --git a/src/components/Blockly/blocks/sensebox-led.js b/src/components/Blockly/blocks/sensebox-led.js new file mode 100644 index 0000000..ef9089a --- /dev/null +++ b/src/components/Blockly/blocks/sensebox-led.js @@ -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'); + } +}; \ No newline at end of file diff --git a/src/components/Blockly/generator/index.js b/src/components/Blockly/generator/index.js index b86e0df..f4370fc 100644 --- a/src/components/Blockly/generator/index.js +++ b/src/components/Blockly/generator/index.js @@ -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'; diff --git a/src/components/Blockly/generator/sensebox-led.js b/src/components/Blockly/generator/sensebox-led.js new file mode 100644 index 0000000..7b76c56 --- /dev/null +++ b/src/components/Blockly/generator/sensebox-led.js @@ -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 \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; +}; \ No newline at end of file diff --git a/src/components/Blockly/toolbox/Toolbox.js b/src/components/Blockly/toolbox/Toolbox.js index cfa858b..0a1f3af 100644 --- a/src/components/Blockly/toolbox/Toolbox.js +++ b/src/components/Blockly/toolbox/Toolbox.js @@ -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 { + + + From ab016610bd4c5405fda020886b426aadeb35f9cf Mon Sep 17 00:00:00 2001 From: Delucse <46593742+Delucse@users.noreply.github.com> Date: Thu, 10 Sep 2020 13:46:31 +0200 Subject: [PATCH 02/10] checking the Blocks with the solution using the XML strings --- src/components/Blockly/BlocklyWindow.js | 1 - src/components/Tutorial/Instruction.js | 3 - src/components/Tutorial/SolutionCheck.js | 10 ++- src/components/Tutorial/Tutorial.js | 1 - src/components/Tutorial/compareXml.js | 78 ++++++++++++++++++++++++ src/components/Tutorial/tutorials.js | 17 ++++++ 6 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 src/components/Tutorial/compareXml.js diff --git a/src/components/Blockly/BlocklyWindow.js b/src/components/Blockly/BlocklyWindow.js index ca70826..93a0633 100644 --- a/src/components/Blockly/BlocklyWindow.js +++ b/src/components/Blockly/BlocklyWindow.js @@ -38,7 +38,6 @@ class BlocklyWindow extends Component { } render() { - console.log(this.props.initialXml); return ( diff --git a/src/components/Tutorial/SolutionCheck.js b/src/components/Tutorial/SolutionCheck.js index bf05749..6e2eefc 100644 --- a/src/components/Tutorial/SolutionCheck.js +++ b/src/components/Tutorial/SolutionCheck.js @@ -8,6 +8,7 @@ import * as Blockly from 'blockly/core'; import Compile from '../Compile'; import { tutorials } from './tutorials'; +import { checkXml } from './compareXml'; import { withStyles } from '@material-ui/core/styles'; import IconButton from '@material-ui/core/IconButton'; @@ -49,7 +50,7 @@ class SolutionCheck extends Component { check = () => { const workspace = Blockly.getMainWorkspace(); - var msg = tutorials[this.props.currentTutorialId].test(workspace); + var msg = checkXml(tutorials[this.props.currentTutorialId].solution, this.props.xml); this.props.tutorialCheck(msg.type); this.setState({ msg, open: true }); } @@ -97,13 +98,16 @@ class SolutionCheck extends Component { }; } + SolutionCheck.propTypes = { tutorialCheck: PropTypes.func.isRequired, - currentTutorialId: PropTypes.number + currentTutorialId: PropTypes.number, + xml: PropTypes.string.isRequired }; const mapStateToProps = state => ({ - currentTutorialId: state.tutorial.currentId + currentTutorialId: state.tutorial.currentId, + xml: state.workspace.code.xml }); export default connect(mapStateToProps, { tutorialCheck })(withStyles(styles, {withTheme: true})(SolutionCheck)); diff --git a/src/components/Tutorial/Tutorial.js b/src/components/Tutorial/Tutorial.js index 710db70..cba3ba7 100644 --- a/src/components/Tutorial/Tutorial.js +++ b/src/components/Tutorial/Tutorial.js @@ -43,7 +43,6 @@ class Tutorial extends Component { render() { var currentTutorialId = this.props.currentTutorialId; - console.log(this.props); return ( !Number.isInteger(currentTutorialId) || currentTutorialId+1 < 1 || currentTutorialId+1 > tutorials.length ? diff --git a/src/components/Tutorial/compareXml.js b/src/components/Tutorial/compareXml.js new file mode 100644 index 0000000..5ca4619 --- /dev/null +++ b/src/components/Tutorial/compareXml.js @@ -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'}; +}; diff --git a/src/components/Tutorial/tutorials.js b/src/components/Tutorial/tutorials.js index e16d078..5cdc203 100644 --- a/src/components/Tutorial/tutorials.js +++ b/src/components/Tutorial/tutorials.js @@ -18,6 +18,23 @@ export const tutorials = [ ` }, + "solution": ` + + + + + + + + TRUE + + + + + + + + `, "test": function(workspace){ var wifi = workspace.getBlocksByType('sensebox_wifi'); // result is an array with Blocks as objects if(wifi.length > 0){ From 33445cf67c2a604b2cd91d2cf6a341f9f61bca2a Mon Sep 17 00:00:00 2001 From: Delucse <46593742+Delucse@users.noreply.github.com> Date: Fri, 11 Sep 2020 09:08:31 +0200 Subject: [PATCH 03/10] tutorial.json --- src/components/Tutorial/tutorials.json | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/components/Tutorial/tutorials.json diff --git a/src/components/Tutorial/tutorials.json b/src/components/Tutorial/tutorials.json new file mode 100644 index 0000000..e185ffc --- /dev/null +++ b/src/components/Tutorial/tutorials.json @@ -0,0 +1,43 @@ +[ + { + "id": 1, + "title": "Erste Schritte", + "steps": [ + { + "id": 1, + "type": "instruction", + "headline": "Erste Schritte", + "text1": "In diesem Tutorial lernst du die ersten Schritte mit der senseBox kennen. Du erstellst ein erstes Programm, baust einen ersten Schaltkreis auf und lernst, wie du das Programm auf die senseBox MCU überträgst", + "hardware": ["senseboxmcu", "led", "breadboard", "jst-adapter", "resistor"], + "requirements": [] + }, + { + "id": 2, + "type": "instruction", + "headline": "Aufbau der Schaltung", + "text1": "Stecke die LED auf das Breadboard und verbinde diese mithile des Widerstandes und dem JST Kabel mit dem Port Digital/Analog 1" + }, + { + "id": 3, + "type": "instruction", + "headline": "Programmierung", + "text1": "Jedes Programm für die senseBox besteht aus zwei Funktionen. Die Setup () Funktion wird zu Begin einmalig ausgeführt und der Programmcode Schrittweise ausgeführt. Nachdem die Setup () Funktion durchlaufen worden ist wird der Programmcode aus der zweiten Funktion, der Endlosschleife, fortlaufend wiederholt.", + "xml": "" + }, + { + "id": 4, + "type": "instruction", + "headline": "Leuchten der LED", + "text1": "Um nun die LED zum leuchten zu bringen wird folgender Block in die Endlosschleife eingefügt. Der Block bietet dir auszuwählen an welchen Pin die LED angeschlossen wurd und ob diese ein oder ausgeschaltet werden soll.", + "xml": "" + }, + { + "id": 5, + "type": "task", + "headline": "Aufgabe 1", + "text1": "Verwenden den Block zum leuchten der LED und übertrage dein erstes Programm auf die senseBox MCU", + "xml": "" + } + ] + } +] From 6c3709fde87040ac23d328882fd27f7dbe5c2e6f Mon Sep 17 00:00:00 2001 From: Delucse <46593742+Delucse@users.noreply.github.com> Date: Fri, 11 Sep 2020 10:13:33 +0200 Subject: [PATCH 04/10] adjustment of the tutorial component and all dependencies to the new tutorial.json --- src/actions/tutorialActions.js | 16 +- src/actions/types.js | 2 +- src/components/Blockly/BlocklyWindow.js | 18 +- src/components/Tutorial/Assessment.js | 53 +++++ src/components/Tutorial/Instruction.js | 45 ++-- src/components/Tutorial/SolutionCheck.js | 81 ++++--- src/components/Tutorial/StepperHorizontal.js | 14 +- src/components/Tutorial/StepperVertical.js | 211 +++++-------------- src/components/Tutorial/Tutorial.js | 93 +++----- src/components/Tutorial/TutorialBody.js | 128 +++++++++++ src/components/Tutorial/TutorialHome.js | 7 +- src/components/Tutorial/tutorials.json | 11 +- src/components/WorkspaceStats.js | 4 +- src/reducers/tutorialReducer.js | 9 +- 14 files changed, 368 insertions(+), 324 deletions(-) create mode 100644 src/components/Tutorial/Assessment.js create mode 100644 src/components/Tutorial/TutorialBody.js diff --git a/src/actions/tutorialActions.js b/src/actions/tutorialActions.js index edf070c..147c97b 100644 --- a/src/actions/tutorialActions.js +++ b/src/actions/tutorialActions.js @@ -1,4 +1,4 @@ -import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_LEVEL } from './types'; +import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_STEP } from './types'; export const tutorialChange = () => (dispatch) => { dispatch({ @@ -30,13 +30,6 @@ export const storeTutorialXml = (code) => (dispatch, getState) => { } }; -// level = "instruction" or "assessment" -export const setTutorialLevel = (level) => (dispatch) => { - dispatch({ - type: TUTORIAL_LEVEL, - payload: level - }); -} export const tutorialId = (id) => (dispatch) => { dispatch({ @@ -44,3 +37,10 @@ export const tutorialId = (id) => (dispatch) => { payload: id }); }; + +export const tutorialStep = (step) => (dispatch) => { + dispatch({ + type: TUTORIAL_STEP, + payload: step + }); +}; diff --git a/src/actions/types.js b/src/actions/types.js index 9d26386..27bd18d 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -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'; diff --git a/src/components/Blockly/BlocklyWindow.js b/src/components/Blockly/BlocklyWindow.js index 93a0633..1ee6baa 100644 --- a/src/components/Blockly/BlocklyWindow.js +++ b/src/components/Blockly/BlocklyWindow.js @@ -26,16 +26,18 @@ class BlocklyWindow extends Component { this.props.onChangeWorkspace(event); Blockly.Events.disableOrphans(event); }); + Blockly.svgResize(workspace); } - componentDidUpdate(props) { - if(props.initialXml !== this.props.initialXml){ - // guarantees that the current xml-code (this.props.initialXml) is rendered - const workspace = Blockly.getMainWorkspace(); - workspace.clear(); - Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(this.props.initialXml), workspace); - } - } + // componentDidUpdate(props) { + // const workspace = Blockly.getMainWorkspace(); + // if(props.initialXml !== this.props.initialXml){ + // // guarantees that the current xml-code (this.props.initialXml) is rendered + // workspace.clear(); + // Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(this.props.initialXml), workspace); + // } + // Blockly.svgResize(workspace); + // } render() { return ( diff --git a/src/components/Tutorial/Assessment.js b/src/components/Tutorial/Assessment.js new file mode 100644 index 0000000..be6fe8b --- /dev/null +++ b/src/components/Tutorial/Assessment.js @@ -0,0 +1,53 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; + +import BlocklyWindow from '../Blockly/BlocklyWindow'; +import SolutionCheck from './SolutionCheck'; +import CodeViewer from '../CodeViewer'; + +import Grid from '@material-ui/core/Grid'; +import Card from '@material-ui/core/Card'; +import Typography from '@material-ui/core/Typography'; + +class Assessment extends Component { + + render() { + var currentTutorialId = this.props.currentTutorialId; + var step = this.props.step + return ( +
+ {step.headline} + + + + + + + + Arbeitsauftrag + {step.text1} + +
+ +
+
+
+
+ ); + }; +} + +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); diff --git a/src/components/Tutorial/Instruction.js b/src/components/Tutorial/Instruction.js index bd6a406..83c192a 100644 --- a/src/components/Tutorial/Instruction.js +++ b/src/components/Tutorial/Instruction.js @@ -4,36 +4,35 @@ import { connect } from 'react-redux'; import BlocklyWindow from '../Blockly/BlocklyWindow'; -import { tutorials } from './tutorials'; - import Grid from '@material-ui/core/Grid'; - +import Typography from '@material-ui/core/Typography'; class Instruction extends Component { render() { - var currentTutorialId = this.props.currentTutorialId; + var step = this.props.step; return ( - tutorials[currentTutorialId].instruction ? -
-

{tutorials[currentTutorialId].instruction.description}

- {tutorials[currentTutorialId].instruction.xml ? - - - - +
+ {step.headline} + {step.text1} + {step.hardware && step.hardware.length > 0 ? 'Hardware: todo' : null} + {step.requirements && step.requirements.length > 0 ? 'Voraussetzungen: todo' : null} + {step.xml ? + + + - : null } -
- : null +
+ : null } +
); }; } diff --git a/src/components/Tutorial/SolutionCheck.js b/src/components/Tutorial/SolutionCheck.js index 6e2eefc..87f65cb 100644 --- a/src/components/Tutorial/SolutionCheck.js +++ b/src/components/Tutorial/SolutionCheck.js @@ -3,11 +3,9 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { tutorialCheck } from '../../actions/tutorialActions'; -import * as Blockly from 'blockly/core'; - import Compile from '../Compile'; -import { tutorials } from './tutorials'; +import tutorials from './tutorials.json'; import { checkXml } from './compareXml'; import { withStyles } from '@material-ui/core/styles'; @@ -49,51 +47,50 @@ class SolutionCheck extends Component { } check = () => { - const workspace = Blockly.getMainWorkspace(); - var msg = checkXml(tutorials[this.props.currentTutorialId].solution, this.props.xml); + const tutorial = tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0]; + const step = tutorial.steps[this.props.activeStep]; + var msg = checkXml(step.xml, this.props.xml); this.props.tutorialCheck(msg.type); this.setState({ msg, open: true }); } render() { return ( - tutorials[this.props.currentTutorialId].test ? -
- - this.check()} - > - - - - - {this.state.msg.type === 'error' ? 'Fehler' : 'Erfolg'} - - {this.state.msg.text} - {this.state.msg.type === 'success' ? -
- - -
- : null} -
- - - -
-
- : null + + : null} + + + + + + ); }; } @@ -102,11 +99,13 @@ class SolutionCheck extends Component { SolutionCheck.propTypes = { tutorialCheck: PropTypes.func.isRequired, currentTutorialId: PropTypes.number, + activeStep: PropTypes.number.isRequired, xml: PropTypes.string.isRequired }; const mapStateToProps = state => ({ currentTutorialId: state.tutorial.currentId, + activeStep: state.tutorial.activeStep, xml: state.workspace.code.xml }); diff --git a/src/components/Tutorial/StepperHorizontal.js b/src/components/Tutorial/StepperHorizontal.js index a914956..cd520ae 100644 --- a/src/components/Tutorial/StepperHorizontal.js +++ b/src/components/Tutorial/StepperHorizontal.js @@ -6,7 +6,7 @@ import { withRouter } from 'react-router-dom'; import clsx from 'clsx'; -import { tutorials } from './tutorials'; +import tutorials from './tutorials.json'; import { fade } from '@material-ui/core/styles/colorManipulator'; import { withStyles } from '@material-ui/core/styles'; @@ -56,22 +56,22 @@ class StepperHorizontal extends Component { return (
-
: ''}> -

{tutorials[tutorialId].title}

+

{tutorials.filter(tutorial => tutorial.id === tutorialId)[0].title}

diff --git a/src/components/Tutorial/StepperVertical.js b/src/components/Tutorial/StepperVertical.js index 44187c3..e46041a 100644 --- a/src/components/Tutorial/StepperVertical.js +++ b/src/components/Tutorial/StepperVertical.js @@ -1,17 +1,13 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import { tutorialStep } from '../../actions/tutorialActions'; import { withRouter, Link } from 'react-router-dom'; import clsx from 'clsx'; -import { tutorials } from './tutorials'; - -import { fade } from '@material-ui/core/styles/colorManipulator'; import { withStyles } from '@material-ui/core/styles'; -import withWidth, { isWidthUp } from '@material-ui/core/withWidth'; -import Button from '@material-ui/core/Button'; import Stepper from '@material-ui/core/Stepper'; import Step from '@material-ui/core/Step'; import StepLabel from '@material-ui/core/StepLabel'; @@ -26,176 +22,74 @@ const styles = (theme) => ({ borderStyle: `solid`, // borderWidth: '2px', borderRadius: '50%', + borderColor: theme.palette.secondary.main, width: '12px', height: '12px', - margin: '0 auto' - }, - stepIconMedium: { - width: '18px', - height: '18px', + margin: '0 auto', }, stepIconLarge: { width: '24px', height: '24px' }, - stepIconTransparent: { - borderColor: `transparent`, - cursor: 'default' + stepIconActive: { + backgroundColor: theme.palette.secondary.main }, - stepIconSuccess: { - borderColor: theme.palette.primary.main, - }, - stepIconError: { - borderColor: theme.palette.error.dark, - }, - stepIconOther: { - borderColor: theme.palette.secondary.main, - }, - stepIconActiveSuccess: { - backgroundColor: fade(theme.palette.primary.main, 0.6) - }, - stepIconActiveError: { - backgroundColor: fade(theme.palette.error.dark, 0.6) - }, - stepIconActiveOther: { - backgroundColor: fade(theme.palette.secondary.main, 0.6) - }, - progress: { - position: 'absolute', - top: 0, - right: 0, - marginRight: '5px', - width: '3px', - }, - progressForeground: { - backgroundColor: theme.palette.primary.main - }, - progressBackground: { - backgroundColor: fade(theme.palette.primary.main, 0.2), - height: '100%', - borderRadius: '2px' + connector: { + height: '10px', + borderLeft: `3px solid ${theme.palette.primary.main}`, + margin: '5px auto' } }); class StepperVertical extends Component { - constructor(props){ - super(props); - this.state = { - tutorialArray: props.currentTutorialId === 0 ? - tutorials.slice(props.currentTutorialId, props.currentTutorialId+5) - : props.currentTutorialId === 1 ? - tutorials.slice(props.currentTutorialId-1, props.currentTutorialId+4) - : props.currentTutorialId === tutorials.length-1 ? - tutorials.slice(props.currentTutorialId-4, props.currentTutorialId+5) - : props.currentTutorialId === tutorials.length-2 ? - tutorials.slice(props.currentTutorialId-3, props.currentTutorialId+4) - : tutorials.slice(props.currentTutorialId-2, props.currentTutorialId+3), - selectedVerticalTutorialId: props.currentTutorialId - }; + componentDidMount(){ + this.props.tutorialStep(0); } componentDidUpdate(props){ - if(props.currentTutorialId !== this.props.currentTutorialId){ - this.setState({ - tutorialArray: this.props.currentTutorialId === 0 ? - tutorials.slice(this.props.currentTutorialId, this.props.currentTutorialId+5) - : this.props.currentTutorialId === 1 ? - tutorials.slice(this.props.currentTutorialId-1, this.props.currentTutorialId+4) - : this.props.currentTutorialId === tutorials.length-1 ? - tutorials.slice(this.props.currentTutorialId-4, this.props.currentTutorialId+5) - : this.props.currentTutorialId === tutorials.length-2 ? - tutorials.slice(this.props.currentTutorialId-3, this.props.currentTutorialId+4) - : tutorials.slice(this.props.currentTutorialId-2, this.props.currentTutorialId+3), - selectedVerticalTutorialId: this.props.currentTutorialId - }); + if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){ + this.props.tutorialStep(0); } } - verticalStepper = (step) => { - var newTutorialId = this.state.selectedVerticalTutorialId + step; - var tutorialArray = newTutorialId === 0 ? - tutorials.slice(newTutorialId, newTutorialId+5) - : newTutorialId === 1 ? - tutorials.slice(newTutorialId-1, newTutorialId+4) - : newTutorialId === tutorials.length-1 ? - tutorials.slice(newTutorialId-4, newTutorialId+5) - : newTutorialId === tutorials.length-2 ? - tutorials.slice(newTutorialId-3, newTutorialId+4) - : tutorials.slice(newTutorialId-2, newTutorialId+3); - this.setState({ tutorialArray: tutorialArray, selectedVerticalTutorialId: newTutorialId }); - } - render() { - var tutorialId = this.props.currentTutorialId; - var selectedVerticalTutorialId = this.state.selectedVerticalTutorialId; + var steps = this.props.steps; + var activeStep = this.props.activeStep; return ( - isWidthUp('sm', this.props.width) ? -
- -
-
-
-
-
-
-
-
} - 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 ( - - 0 ? tutorial.title : ''} placement='right' arrow > - - - - - - - )})} - -
- - - : null +
+
} + classes={{root: this.props.classes.verticalStepper}} + > + {steps.map((step, i) => { + // var tutorialStatus = this.props.status[verticalTutorialId-1].status === 'success' ? 'Success' : + // this.props.status[verticalTutorialId-1].status === 'error' ? 'Error' : 'Other'; + return ( + + + {this.props.tutorialStep(i)}}> + + + + + + )})} + + ); }; } @@ -204,13 +98,16 @@ class StepperVertical extends Component { StepperVertical.propTypes = { status: PropTypes.array.isRequired, change: PropTypes.number.isRequired, - currentTutorialId: PropTypes.number.isRequired + currentTutorialId: PropTypes.number.isRequired, + activeStep: PropTypes.number.isRequired, + tutorialStep: PropTypes.func.isRequired }; const mapStateToProps = state => ({ change: state.tutorial.change, status: state.tutorial.status, - currentTutorialId: state.tutorial.currentId + currentTutorialId: state.tutorial.currentId, + activeStep: state.tutorial.activeStep }); -export default connect(mapStateToProps, null)(withRouter(withStyles(styles, {withTheme: true})(withWidth()(StepperVertical)))); +export default connect(mapStateToProps, { tutorialStep })(withRouter(withStyles(styles, {withTheme: true})(StepperVertical))); diff --git a/src/components/Tutorial/Tutorial.js b/src/components/Tutorial/Tutorial.js index cba3ba7..621d221 100644 --- a/src/components/Tutorial/Tutorial.js +++ b/src/components/Tutorial/Tutorial.js @@ -1,35 +1,29 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { tutorialId, setTutorialLevel } from '../../actions/tutorialActions'; +import { tutorialId, tutorialStep } from '../../actions/tutorialActions'; import Breadcrumbs from '../Breadcrumbs'; import StepperHorizontal from './StepperHorizontal'; import StepperVertical from './StepperVertical'; import Instruction from './Instruction'; -import BlocklyWindow from '../Blockly/BlocklyWindow'; -import SolutionCheck from './SolutionCheck'; -import CodeViewer from '../CodeViewer'; +import Assessment from './Assessment'; import NotFound from '../NotFound'; -import { tutorials } from './tutorials'; +import tutorials from './tutorials.json'; -import withWidth, { isWidthUp } from '@material-ui/core/withWidth'; -import Tabs from '@material-ui/core/Tabs'; -import Tab from '@material-ui/core/Tab'; -import Grid from '@material-ui/core/Grid'; import Card from '@material-ui/core/Card'; +import Button from '@material-ui/core/Button'; class Tutorial extends Component { componentDidMount(){ - this.props.tutorialId(Number(this.props.match.params.tutorialId)-1); + this.props.tutorialId(Number(this.props.match.params.tutorialId)); } componentDidUpdate(props, state){ - if(props.currentTutorialId+1 !== Number(this.props.match.params.tutorialId)){ - this.props.tutorialId(Number(this.props.match.params.tutorialId)-1); - this.props.setTutorialLevel('instruction'); + if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){ + this.props.tutorialId(Number(this.props.match.params.tutorialId)); } } @@ -37,78 +31,55 @@ class Tutorial extends Component { this.props.tutorialId(null); } - onChange = (e, value) => { - this.props.setTutorialLevel(value); - } - render() { var currentTutorialId = this.props.currentTutorialId; + var tutorial = tutorials.filter(tutorial => tutorial.id === currentTutorialId)[0]; + var steps = tutorial ? tutorial.steps : null; + var step = steps ? steps[this.props.activeStep] : null; return ( - !Number.isInteger(currentTutorialId) || currentTutorialId+1 < 1 || currentTutorialId+1 > tutorials.length ? + !Number.isInteger(currentTutorialId) || currentTutorialId < 1 || currentTutorialId > tutorials.length ? : -
- +
+ - + -
- +
+ - {/* width of vertical stepper is 30px*/} - - - - - + + {step ? + step.type === 'instruction' ? + + : // if step.type === 'assessment' + : null} -
- {this.props.level === 'instruction' ? - : null } - {this.props.level === 'assessment' ? - - - - - - - - Hier könnte die Problemstellung stehen. - -
- -
-
-
- : null } -
-
-
+
+ + +
+
+
); }; } 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); diff --git a/src/components/Tutorial/TutorialBody.js b/src/components/Tutorial/TutorialBody.js new file mode 100644 index 0000000..f59cfd9 --- /dev/null +++ b/src/components/Tutorial/TutorialBody.js @@ -0,0 +1,128 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { tutorialId, tutorialStep } from '../../actions/tutorialActions'; + +import Breadcrumbs from '../Breadcrumbs'; +import StepperHorizontal from './StepperHorizontal'; +import StepperVertical from './StepperVertical'; +import Instruction from './Instruction'; +import Assessment from './Assessment'; +import NotFound from '../NotFound'; + +import tutorials from './tutorials.json'; + +import withWidth, { isWidthUp } from '@material-ui/core/withWidth'; +import Card from '@material-ui/core/Card'; +import Button from '@material-ui/core/Button'; + +class Tutorial extends Component { + + componentDidMount(){ + this.props.tutorialId(Number(this.props.match.params.tutorialId)); + } + + componentDidUpdate(props, state){ + if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){ + this.props.tutorialId(Number(this.props.match.params.tutorialId)); + } + } + + componentWillUnmount(){ + this.props.tutorialId(null); + } + + render() { + var currentTutorialId = this.props.currentTutorialId; + var tutorial = tutorials.filter(tutorial => tutorial.id === currentTutorialId)[0]; + var step = tutorial.steps[this.props.activeStep]; + + const Tutorial2 = () => ( +
+ + + + +
+ + +
+ {step.type === 'instruction' ? + + : } + +
+ + +
+
+
+
+ ); + return ( + !Number.isInteger(currentTutorialId) || currentTutorialId < 1 || currentTutorialId > tutorials.length ? + + : + + ); + }; +} + +Tutorial.propTypes = { + tutorialId: PropTypes.func.isRequired, + currentTutorialId: PropTypes.number, + status: PropTypes.array.isRequired, + change: PropTypes.number.isRequired, + activeStep: PropTypes.number.isRequired +}; + +const mapStateToProps = state => ({ + change: state.tutorial.change, + status: state.tutorial.status, + currentTutorialId: state.tutorial.currentId, + activeStep: state.tutorial.activeStep +}); + +export default connect(mapStateToProps, { tutorialId, tutorialStep })(withWidth()(Tutorial)); + + + +// +// + +// +// {/* width of vertical stepper is 30px*/} +// +// +// +// +// +// +//
+// {this.props.level === 'instruction' ? +// : null } +// {this.props.level === 'assessment' ? +// +// +// +// +// +// +// +// Hier könnte die Problemstellung stehen. +// +//
+// +//
+//
+//
+// : null } +//
+//
+//
diff --git a/src/components/Tutorial/TutorialHome.js b/src/components/Tutorial/TutorialHome.js index c00dc08..6eec3e0 100644 --- a/src/components/Tutorial/TutorialHome.js +++ b/src/components/Tutorial/TutorialHome.js @@ -6,7 +6,7 @@ import clsx from 'clsx'; import Breadcrumbs from '../Breadcrumbs'; -import { tutorials } from './tutorials'; +import tutorials from './tutorials.json'; import { Link } from 'react-router-dom'; @@ -59,9 +59,9 @@ class TutorialHome extends Component { this.props.status[i].status === 'error' ? 'Error' : 'Other'; return ( - + - {tutorials[i].title} + {tutorial.title} {tutorialStatus !== 'Other' ?
@@ -72,7 +72,6 @@ class TutorialHome extends Component { } - )})} diff --git a/src/components/Tutorial/tutorials.json b/src/components/Tutorial/tutorials.json index e185ffc..e2fcef8 100644 --- a/src/components/Tutorial/tutorials.json +++ b/src/components/Tutorial/tutorials.json @@ -4,38 +4,33 @@ "title": "Erste Schritte", "steps": [ { - "id": 1, "type": "instruction", "headline": "Erste Schritte", - "text1": "In diesem Tutorial lernst du die ersten Schritte mit der senseBox kennen. Du erstellst ein erstes Programm, baust einen ersten Schaltkreis auf und lernst, wie du das Programm auf die senseBox MCU überträgst", + "text1": "In diesem Tutorial lernst du die ersten Schritte mit der senseBox kennen. Du erstellst ein erstes Programm, baust einen ersten Schaltkreis auf und lernst, wie du das Programm auf die senseBox MCU überträgst.", "hardware": ["senseboxmcu", "led", "breadboard", "jst-adapter", "resistor"], "requirements": [] }, { - "id": 2, "type": "instruction", "headline": "Aufbau der Schaltung", - "text1": "Stecke die LED auf das Breadboard und verbinde diese mithile des Widerstandes und dem JST Kabel mit dem Port Digital/Analog 1" + "text1": "Stecke die LED auf das Breadboard und verbinde diese mithile des Widerstandes und dem JST Kabel mit dem Port Digital/Analog 1." }, { - "id": 3, "type": "instruction", "headline": "Programmierung", "text1": "Jedes Programm für die senseBox besteht aus zwei Funktionen. Die Setup () Funktion wird zu Begin einmalig ausgeführt und der Programmcode Schrittweise ausgeführt. Nachdem die Setup () Funktion durchlaufen worden ist wird der Programmcode aus der zweiten Funktion, der Endlosschleife, fortlaufend wiederholt.", "xml": "" }, { - "id": 4, "type": "instruction", "headline": "Leuchten der LED", "text1": "Um nun die LED zum leuchten zu bringen wird folgender Block in die Endlosschleife eingefügt. Der Block bietet dir auszuwählen an welchen Pin die LED angeschlossen wurd und ob diese ein oder ausgeschaltet werden soll.", "xml": "" }, { - "id": 5, "type": "task", "headline": "Aufgabe 1", - "text1": "Verwenden den Block zum leuchten der LED und übertrage dein erstes Programm auf die senseBox MCU", + "text1": "Verwenden den Block zum leuchten der LED und übertrage dein erstes Programm auf die senseBox MCU.", "xml": "" } ] diff --git a/src/components/WorkspaceStats.js b/src/components/WorkspaceStats.js index b18d4e3..9a98571 100644 --- a/src/components/WorkspaceStats.js +++ b/src/components/WorkspaceStats.js @@ -42,7 +42,7 @@ class WorkspaceStats extends Component { style={{ marginRight: '1rem' }} color="primary" 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 */} @@ -58,7 +58,7 @@ class WorkspaceStats extends Component { style={{ marginRight: '1rem' }} color="primary" 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 */} diff --git a/src/reducers/tutorialReducer.js b/src/reducers/tutorialReducer.js index e951cb0..3769c05 100644 --- a/src/reducers/tutorialReducer.js +++ b/src/reducers/tutorialReducer.js @@ -1,4 +1,4 @@ -import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_LEVEL } from '../actions/types'; +import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_STEP } from '../actions/types'; import { tutorials } from '../components/Tutorial/tutorials'; @@ -7,7 +7,8 @@ const initialState = { JSON.parse(window.localStorage.getItem('tutorial')) : new Array(tutorials.length).fill({}), level: 'instruction', - currentId: null, + currentId: 0, + activeStep: 0, change: 0 }; @@ -32,10 +33,10 @@ export default function(state = initialState, action){ ...state, currentId: action.payload } - case TUTORIAL_LEVEL: + case TUTORIAL_STEP: return { ...state, - level: action.payload + activeStep: action.payload } default: return state; From 825075d65620bf450ea930b1988e17460c813be5 Mon Sep 17 00:00:00 2001 From: Delucse <46593742+Delucse@users.noreply.github.com> Date: Fri, 11 Sep 2020 11:00:57 +0200 Subject: [PATCH 05/10] next and back step - buttons --- src/components/Tutorial/Assessment.js | 2 +- src/components/Tutorial/Instruction.js | 12 ++++++++---- src/components/Tutorial/StepperVertical.js | 8 ++++---- src/components/Tutorial/Tutorial.js | 10 +++++----- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/components/Tutorial/Assessment.js b/src/components/Tutorial/Assessment.js index be6fe8b..88c3cc9 100644 --- a/src/components/Tutorial/Assessment.js +++ b/src/components/Tutorial/Assessment.js @@ -18,7 +18,7 @@ class Assessment extends Component { return (
{step.headline} - + diff --git a/src/components/Tutorial/Instruction.js b/src/components/Tutorial/Instruction.js index 83c192a..2b0c8d8 100644 --- a/src/components/Tutorial/Instruction.js +++ b/src/components/Tutorial/Instruction.js @@ -11,14 +11,18 @@ class Instruction extends Component { render() { var step = this.props.step; + var isHardware = step.hardware && step.hardware.length > 0; + var areRequirements = step.requirements && step.requirements.length > 0; return (
{step.headline} - {step.text1} - {step.hardware && step.hardware.length > 0 ? 'Hardware: todo' : null} - {step.requirements && step.requirements.length > 0 ? 'Voraussetzungen: todo' : null} + {step.text1} + {isHardware ? + Hardware: todo : null} + {areRequirements > 0 ? + Voraussetzungen: todo : null} {step.xml ? - + ({ connector: { height: '10px', borderLeft: `3px solid ${theme.palette.primary.main}`, - margin: '5px auto' + margin: 'auto' } }); @@ -70,7 +70,7 @@ class StepperVertical extends Component { return ( - {this.props.tutorialStep(i)}}> +
{this.props.tutorialStep(i)}}> - +
)})} diff --git a/src/components/Tutorial/Tutorial.js b/src/components/Tutorial/Tutorial.js index 621d221..41a560a 100644 --- a/src/components/Tutorial/Tutorial.js +++ b/src/components/Tutorial/Tutorial.js @@ -47,17 +47,17 @@ class Tutorial extends Component {
- - + {/* calc(Card-padding: 10px + Button-height: 35px + Button-marginTop: 15px)*/} + {step ? step.type === 'instruction' ? : // if step.type === 'assessment' : null} -
- - +
+ +
From f19842ccaebdbfd8a4badd763142db33d3bd0de6 Mon Sep 17 00:00:00 2001 From: Delucse <46593742+Delucse@users.noreply.github.com> Date: Fri, 11 Sep 2020 17:31:06 +0200 Subject: [PATCH 06/10] status of tutorial and belonging tasks --- src/actions/tutorialActions.js | 39 +++++++++---- src/components/Tutorial/Assessment.js | 16 +++-- src/components/Tutorial/StepperHorizontal.js | 61 ++++++++++++-------- src/components/Tutorial/StepperVertical.js | 30 +++++++--- src/components/Tutorial/Tutorial.js | 4 +- src/components/Tutorial/TutorialHome.js | 56 ++++++++++++------ src/reducers/tutorialReducer.js | 13 ++--- 7 files changed, 147 insertions(+), 72 deletions(-) diff --git a/src/actions/tutorialActions.js b/src/actions/tutorialActions.js index 147c97b..31e3b6e 100644 --- a/src/actions/tutorialActions.js +++ b/src/actions/tutorialActions.js @@ -1,15 +1,25 @@ 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({ type: TUTORIAL_CHANGE }); }; -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 activeStep = getState().tutorial.activeStep; + var steps = tutorials.filter(tutorial => tutorial.id === id)[0].steps; + var tasks = steps.filter(step => step.type === 'task'); + var tasksIndex = tasks.indexOf(steps[activeStep]); + var tutorialsStatusIndex = tutorialsStatus.findIndex(tutorialStatus => tutorialStatus.id === id); + tutorialsStatus[tutorialsStatusIndex].tasks[tasksIndex] = { + ...tutorialsStatus[tutorialsStatusIndex].tasks[tasksIndex], + type: status + }; dispatch({ type: status === 'success' ? TUTORIAL_SUCCESS : TUTORIAL_ERROR, payload: tutorialsStatus @@ -19,14 +29,23 @@ 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 tasks = steps.filter(step => step.type === 'task'); + var tasksIndex = tasks.indexOf(steps[activeStep]); + var tutorialsStatusIndex = tutorialsStatus.findIndex(tutorialStatus => tutorialStatus.id === id); + tutorialsStatus[tutorialsStatusIndex].tasks[tasksIndex] = { + ...tutorialsStatus[tutorialsStatusIndex].tasks[tasksIndex], + xml: code + }; + dispatch({ + type: TUTORIAL_XML, + payload: tutorialsStatus + }); + } } }; diff --git a/src/components/Tutorial/Assessment.js b/src/components/Tutorial/Assessment.js index 88c3cc9..f15f79b 100644 --- a/src/components/Tutorial/Assessment.js +++ b/src/components/Tutorial/Assessment.js @@ -13,20 +13,26 @@ import Typography from '@material-ui/core/Typography'; class Assessment extends Component { render() { - var currentTutorialId = this.props.currentTutorialId; - var step = this.props.step + var tutorialId = this.props.currentTutorialId; + var steps = this.props.steps; + var currentTask = this.props.step; + var tasks = steps.filter(task => task.type === 'task'); + var taskIndex = tasks.indexOf(currentTask); + var status = this.props.status.filter(status => status.id === tutorialId)[0]; + var statusTask = status.tasks[taskIndex] + return (
- {step.headline} + {currentTask.headline} - + Arbeitsauftrag - {step.text1} + {currentTask.text1}
diff --git a/src/components/Tutorial/StepperHorizontal.js b/src/components/Tutorial/StepperHorizontal.js index cd520ae..10aa0ff 100644 --- a/src/components/Tutorial/StepperHorizontal.js +++ b/src/components/Tutorial/StepperHorizontal.js @@ -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,44 @@ 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 steps = this.props.steps; + var tasks = steps.filter(task => task.type === 'task'); + var status = this.props.status.filter(status => status.id === tutorialId)[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 ( -
- - - -
: ''}> -

{tutorials.filter(tutorial => tutorial.id === tutorialId)[0].title}

- - - - +
+ {error || success > 0 ? +
+
+ : null} + {success < 1 && !error ? +
+
+ : null} +
+ + + +
: ''}> +

{tutorials.filter(tutorial => tutorial.id === tutorialId)[0].title}

+ + + + +
); }; diff --git a/src/components/Tutorial/StepperVertical.js b/src/components/Tutorial/StepperVertical.js index 2c2c952..dad7caa 100644 --- a/src/components/Tutorial/StepperVertical.js +++ b/src/components/Tutorial/StepperVertical.js @@ -7,6 +7,7 @@ import { withRouter } from 'react-router-dom'; import clsx from 'clsx'; +import { fade } from '@material-ui/core/styles/colorManipulator'; import { withStyles } from '@material-ui/core/styles'; import Stepper from '@material-ui/core/Stepper'; import Step from '@material-ui/core/Step'; @@ -31,12 +32,24 @@ const styles = (theme) => ({ width: '24px', height: '24px' }, - stepIconActive: { + stepIconLargeSuccess: { + borderColor: theme.palette.primary.main, + }, + stepIconLargeError: { + borderColor: theme.palette.error.dark, + }, + stepIconActiveOther: { backgroundColor: theme.palette.secondary.main }, + stepIconActiveSuccess: { + backgroundColor: fade(theme.palette.primary.main, 0.6) + }, + stepIconActiveError: { + backgroundColor: fade(theme.palette.error.dark, 0.6) + }, connector: { height: '10px', - borderLeft: `3px solid ${theme.palette.primary.main}`, + borderLeft: `2px solid black`, margin: 'auto' } }); @@ -56,6 +69,8 @@ class StepperVertical extends Component { render() { var steps = this.props.steps; var activeStep = this.props.activeStep; + var tasks = steps.filter(task => task.type === 'task'); + var tutorialStatus = this.props.status.filter(status => status.id === this.props.currentTutorialId)[0]; return (
{steps.map((step, i) => { - // var tutorialStatus = this.props.status[verticalTutorialId-1].status === 'success' ? 'Success' : - // this.props.status[verticalTutorialId-1].status === 'error' ? 'Error' : 'Other'; + var tasksIndex = tasks.indexOf(step); + var taskType = tasksIndex > -1 ? tutorialStatus.tasks[tasksIndex].type : null; + var taskStatus = taskType === 'success' ? 'Success' : taskType === 'error' ? 'Error' : 'Other'; return ( @@ -76,10 +92,10 @@ class StepperVertical extends Component { classes={{ root: step.type === 'task' ? i === activeStep ? - clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes.stepIconActive) - : clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge) + 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.stepIconActive) + clsx(this.props.classes.stepIcon, this.props.classes.stepIconActiveOther) : clsx(this.props.classes.stepIcon) }} > diff --git a/src/components/Tutorial/Tutorial.js b/src/components/Tutorial/Tutorial.js index 41a560a..743e6f2 100644 --- a/src/components/Tutorial/Tutorial.js +++ b/src/components/Tutorial/Tutorial.js @@ -43,7 +43,7 @@ class Tutorial extends Component {
- +
@@ -52,7 +52,7 @@ class Tutorial extends Component { {step ? step.type === 'instruction' ? - : // if step.type === 'assessment' + : // if step.type === 'assessment' : null}
diff --git a/src/components/Tutorial/TutorialHome.js b/src/components/Tutorial/TutorialHome.js index 6eec3e0..da4ccfb 100644 --- a/src/components/Tutorial/TutorialHome.js +++ b/src/components/Tutorial/TutorialHome.js @@ -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,21 +59,37 @@ class TutorialHome extends Component {

Tutorial-Übersicht

{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'; + console.log(success); return ( {tutorial.title} - {tutorialStatus !== 'Other' ? -
-
+
+ + {error || success === 1 ? + + : } + {success < 1 && !error ? + + + : null} + +
+
+
+ {error || success === 1 ? -
+ : 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success*100)}% + }
- : null - } +
diff --git a/src/reducers/tutorialReducer.js b/src/reducers/tutorialReducer.js index 3769c05..470b727 100644 --- a/src/reducers/tutorialReducer.js +++ b/src/reducers/tutorialReducer.js @@ -1,13 +1,12 @@ 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 initialState = { - status: window.localStorage.getItem('tutorial') ? - JSON.parse(window.localStorage.getItem('tutorial')) - : new Array(tutorials.length).fill({}), - level: 'instruction', - currentId: 0, + status: window.localStorage.getItem('status') ? + JSON.parse(window.localStorage.getItem('status')) + : tutorials.map(tutorial => {return {id: tutorial.id, tasks: new Array(tutorial.steps.filter(step => step.type === 'task').length).fill({}) };}), + currentId: null, activeStep: 0, change: 0 }; @@ -18,7 +17,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 From e7723487c9eb7c193f1f4ed40fe64fc0290e4a04 Mon Sep 17 00:00:00 2001 From: Delucse <46593742+Delucse@users.noreply.github.com> Date: Fri, 11 Sep 2020 17:47:05 +0200 Subject: [PATCH 07/10] next step button or tutorial-overview button after solving task --- src/components/Tutorial/SolutionCheck.js | 39 ++++--- src/components/Tutorial/TutorialBody.js | 128 ----------------------- 2 files changed, 27 insertions(+), 140 deletions(-) delete mode 100644 src/components/Tutorial/TutorialBody.js diff --git a/src/components/Tutorial/SolutionCheck.js b/src/components/Tutorial/SolutionCheck.js index 87f65cb..b012af2 100644 --- a/src/components/Tutorial/SolutionCheck.js +++ b/src/components/Tutorial/SolutionCheck.js @@ -1,7 +1,9 @@ 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 { withRouter } from 'react-router-dom'; import Compile from '../Compile'; @@ -55,6 +57,7 @@ class SolutionCheck extends Component { } render() { + const steps = tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0].steps; return (
@@ -71,16 +74,27 @@ class SolutionCheck extends Component { {this.state.msg.text} {this.state.msg.type === 'success' ? -
- - +
+ + {this.props.activeStep === steps.length-1 ? + + : + + }
: null} @@ -98,6 +112,7 @@ class SolutionCheck extends Component { SolutionCheck.propTypes = { tutorialCheck: PropTypes.func.isRequired, + tutorialStep: PropTypes.func.isRequired, currentTutorialId: PropTypes.number, activeStep: PropTypes.number.isRequired, xml: PropTypes.string.isRequired @@ -109,4 +124,4 @@ const mapStateToProps = state => ({ 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))); diff --git a/src/components/Tutorial/TutorialBody.js b/src/components/Tutorial/TutorialBody.js deleted file mode 100644 index f59cfd9..0000000 --- a/src/components/Tutorial/TutorialBody.js +++ /dev/null @@ -1,128 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { tutorialId, tutorialStep } from '../../actions/tutorialActions'; - -import Breadcrumbs from '../Breadcrumbs'; -import StepperHorizontal from './StepperHorizontal'; -import StepperVertical from './StepperVertical'; -import Instruction from './Instruction'; -import Assessment from './Assessment'; -import NotFound from '../NotFound'; - -import tutorials from './tutorials.json'; - -import withWidth, { isWidthUp } from '@material-ui/core/withWidth'; -import Card from '@material-ui/core/Card'; -import Button from '@material-ui/core/Button'; - -class Tutorial extends Component { - - componentDidMount(){ - this.props.tutorialId(Number(this.props.match.params.tutorialId)); - } - - componentDidUpdate(props, state){ - if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){ - this.props.tutorialId(Number(this.props.match.params.tutorialId)); - } - } - - componentWillUnmount(){ - this.props.tutorialId(null); - } - - render() { - var currentTutorialId = this.props.currentTutorialId; - var tutorial = tutorials.filter(tutorial => tutorial.id === currentTutorialId)[0]; - var step = tutorial.steps[this.props.activeStep]; - - const Tutorial2 = () => ( -
- - - - -
- - -
- {step.type === 'instruction' ? - - : } - -
- - -
-
-
-
- ); - return ( - !Number.isInteger(currentTutorialId) || currentTutorialId < 1 || currentTutorialId > tutorials.length ? - - : - - ); - }; -} - -Tutorial.propTypes = { - tutorialId: PropTypes.func.isRequired, - currentTutorialId: PropTypes.number, - status: PropTypes.array.isRequired, - change: PropTypes.number.isRequired, - activeStep: PropTypes.number.isRequired -}; - -const mapStateToProps = state => ({ - change: state.tutorial.change, - status: state.tutorial.status, - currentTutorialId: state.tutorial.currentId, - activeStep: state.tutorial.activeStep -}); - -export default connect(mapStateToProps, { tutorialId, tutorialStep })(withWidth()(Tutorial)); - - - -// -// - -// -// {/* width of vertical stepper is 30px*/} -// -// -// -// -// -// -//
-// {this.props.level === 'instruction' ? -// : null } -// {this.props.level === 'assessment' ? -// -// -// -// -// -// -// -// Hier könnte die Problemstellung stehen. -// -//
-// -//
-//
-//
-// : null } -//
-//
-//
From f997bb661ff778c8b844aa8383c0dd02a9a84918 Mon Sep 17 00:00:00 2001 From: Delucse <46593742+Delucse@users.noreply.github.com> Date: Sun, 13 Sep 2020 13:19:09 +0200 Subject: [PATCH 08/10] update initialXml --- src/components/Blockly/BlocklyWindow.js | 26 +++++++++++++------------ src/components/Tutorial/Tutorial.js | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/components/Blockly/BlocklyWindow.js b/src/components/Blockly/BlocklyWindow.js index 1ee6baa..7b66b81 100644 --- a/src/components/Blockly/BlocklyWindow.js +++ b/src/components/Blockly/BlocklyWindow.js @@ -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,6 +22,7 @@ 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); @@ -29,15 +30,15 @@ class BlocklyWindow extends Component { 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 - // workspace.clear(); - // Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(this.props.initialXml), workspace); - // } - // 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 + workspace.clear(); + Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(this.props.initialXml), workspace); + } + Blockly.svgResize(workspace); + } render() { return ( @@ -77,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); diff --git a/src/components/Tutorial/Tutorial.js b/src/components/Tutorial/Tutorial.js index 743e6f2..c5c1d9e 100644 --- a/src/components/Tutorial/Tutorial.js +++ b/src/components/Tutorial/Tutorial.js @@ -48,7 +48,7 @@ class Tutorial extends Component {
{/* calc(Card-padding: 10px + Button-height: 35px + Button-marginTop: 15px)*/} - + {step ? step.type === 'instruction' ? From 6f77b460cb81e249388c3cf6384a031ae424d73e Mon Sep 17 00:00:00 2001 From: Delucse <46593742+Delucse@users.noreply.github.com> Date: Sun, 13 Sep 2020 15:18:11 +0200 Subject: [PATCH 09/10] add/ delete tutorials/steps in initial state of redux based on tutorials.json --- src/reducers/tutorialReducer.js | 43 ++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/reducers/tutorialReducer.js b/src/reducers/tutorialReducer.js index 470b727..f966fcc 100644 --- a/src/reducers/tutorialReducer.js +++ b/src/reducers/tutorialReducer.js @@ -2,10 +2,47 @@ import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORI 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); + } + console.log('tutorial', existingTutorialIds); + console.log('tutorial', status); + 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('status') ? - JSON.parse(window.localStorage.getItem('status')) - : tutorials.map(tutorial => {return {id: tutorial.id, tasks: new Array(tutorial.steps.filter(step => step.type === 'task').length).fill({}) };}), + status: initialStatus(), currentId: null, activeStep: 0, change: 0 From 7300cade5d36f3f9c4736a3685a75ff3f8ac0ed0 Mon Sep 17 00:00:00 2001 From: Delucse <46593742+Delucse@users.noreply.github.com> Date: Sun, 13 Sep 2020 16:02:47 +0200 Subject: [PATCH 10/10] each task (step) has its own id within a tutorial --- src/actions/tutorialActions.js | 8 +--- src/components/Tutorial/Assessment.js | 8 ++-- src/components/Tutorial/SolutionCheck.js | 2 +- src/components/Tutorial/StepperHorizontal.js | 7 ++- src/components/Tutorial/StepperVertical.js | 3 +- src/components/Tutorial/Tutorial.js | 4 +- src/components/Tutorial/TutorialHome.js | 1 - src/components/Tutorial/tutorials.json | 47 ++++++++++++++++++++ src/reducers/tutorialReducer.js | 2 - 9 files changed, 59 insertions(+), 23 deletions(-) diff --git a/src/actions/tutorialActions.js b/src/actions/tutorialActions.js index 31e3b6e..b741c3e 100644 --- a/src/actions/tutorialActions.js +++ b/src/actions/tutorialActions.js @@ -11,11 +11,8 @@ export const tutorialChange = () => (dispatch) => { export const tutorialCheck = (status, step) => (dispatch, getState) => { var tutorialsStatus = getState().tutorial.status; var id = getState().tutorial.currentId; - var activeStep = getState().tutorial.activeStep; - var steps = tutorials.filter(tutorial => tutorial.id === id)[0].steps; - var tasks = steps.filter(step => step.type === 'task'); - var tasksIndex = tasks.indexOf(steps[activeStep]); 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 @@ -34,9 +31,8 @@ export const storeTutorialXml = (code) => (dispatch, getState) => { var steps = tutorials.filter(tutorial => tutorial.id === id)[0].steps; if(steps[activeStep].type === 'task'){ var tutorialsStatus = getState().tutorial.status; - var tasks = steps.filter(step => step.type === 'task'); - var tasksIndex = tasks.indexOf(steps[activeStep]); 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 diff --git a/src/components/Tutorial/Assessment.js b/src/components/Tutorial/Assessment.js index f15f79b..3e2db26 100644 --- a/src/components/Tutorial/Assessment.js +++ b/src/components/Tutorial/Assessment.js @@ -14,13 +14,11 @@ class Assessment extends Component { render() { var tutorialId = this.props.currentTutorialId; - var steps = this.props.steps; var currentTask = this.props.step; - var tasks = steps.filter(task => task.type === 'task'); - var taskIndex = tasks.indexOf(currentTask); var status = this.props.status.filter(status => status.id === tutorialId)[0]; - var statusTask = status.tasks[taskIndex] - + var taskIndex = status.tasks.findIndex(task => task.id === currentTask.id); + var statusTask = status.tasks[taskIndex]; + return (
{currentTask.headline} diff --git a/src/components/Tutorial/SolutionCheck.js b/src/components/Tutorial/SolutionCheck.js index b012af2..3156fc6 100644 --- a/src/components/Tutorial/SolutionCheck.js +++ b/src/components/Tutorial/SolutionCheck.js @@ -52,7 +52,7 @@ class SolutionCheck extends Component { const tutorial = tutorials.filter(tutorial => tutorial.id === this.props.currentTutorialId)[0]; const step = tutorial.steps[this.props.activeStep]; var msg = checkXml(step.xml, this.props.xml); - this.props.tutorialCheck(msg.type); + this.props.tutorialCheck(msg.type, step); this.setState({ msg, open: true }); } diff --git a/src/components/Tutorial/StepperHorizontal.js b/src/components/Tutorial/StepperHorizontal.js index 10aa0ff..b54c1f7 100644 --- a/src/components/Tutorial/StepperHorizontal.js +++ b/src/components/Tutorial/StepperHorizontal.js @@ -52,11 +52,10 @@ class StepperHorizontal extends Component { render() { var tutorialId = this.props.currentTutorialId; - var steps = this.props.steps; - var tasks = steps.filter(task => task.type === 'task'); var status = this.props.status.filter(status => status.id === tutorialId)[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 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 (
diff --git a/src/components/Tutorial/StepperVertical.js b/src/components/Tutorial/StepperVertical.js index dad7caa..dcaf36e 100644 --- a/src/components/Tutorial/StepperVertical.js +++ b/src/components/Tutorial/StepperVertical.js @@ -69,7 +69,6 @@ class StepperVertical extends Component { render() { var steps = this.props.steps; var activeStep = this.props.activeStep; - var tasks = steps.filter(task => task.type === 'task'); var tutorialStatus = this.props.status.filter(status => status.id === this.props.currentTutorialId)[0]; return (
@@ -80,7 +79,7 @@ class StepperVertical extends Component { classes={{root: this.props.classes.verticalStepper}} > {steps.map((step, i) => { - var tasksIndex = tasks.indexOf(step); + 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 ( diff --git a/src/components/Tutorial/Tutorial.js b/src/components/Tutorial/Tutorial.js index c5c1d9e..e2a9d38 100644 --- a/src/components/Tutorial/Tutorial.js +++ b/src/components/Tutorial/Tutorial.js @@ -43,7 +43,7 @@ class Tutorial extends Component {
- +
@@ -52,7 +52,7 @@ class Tutorial extends Component { {step ? step.type === 'instruction' ? - : // if step.type === 'assessment' + : // if step.type === 'assessment' : null}
diff --git a/src/components/Tutorial/TutorialHome.js b/src/components/Tutorial/TutorialHome.js index da4ccfb..3b73ac7 100644 --- a/src/components/Tutorial/TutorialHome.js +++ b/src/components/Tutorial/TutorialHome.js @@ -65,7 +65,6 @@ class TutorialHome extends Component { 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'; - console.log(success); return ( diff --git a/src/components/Tutorial/tutorials.json b/src/components/Tutorial/tutorials.json index e2fcef8..bfba590 100644 --- a/src/components/Tutorial/tutorials.json +++ b/src/components/Tutorial/tutorials.json @@ -4,6 +4,7 @@ "title": "Erste Schritte", "steps": [ { + "id": 1, "type": "instruction", "headline": "Erste Schritte", "text1": "In diesem Tutorial lernst du die ersten Schritte mit der senseBox kennen. Du erstellst ein erstes Programm, baust einen ersten Schaltkreis auf und lernst, wie du das Programm auf die senseBox MCU überträgst.", @@ -11,28 +12,74 @@ "requirements": [] }, { + "id": 2, "type": "instruction", "headline": "Aufbau der Schaltung", "text1": "Stecke die LED auf das Breadboard und verbinde diese mithile des Widerstandes und dem JST Kabel mit dem Port Digital/Analog 1." }, { + "id": 3, "type": "instruction", "headline": "Programmierung", "text1": "Jedes Programm für die senseBox besteht aus zwei Funktionen. Die Setup () Funktion wird zu Begin einmalig ausgeführt und der Programmcode Schrittweise ausgeführt. Nachdem die Setup () Funktion durchlaufen worden ist wird der Programmcode aus der zweiten Funktion, der Endlosschleife, fortlaufend wiederholt.", "xml": "" }, { + "id": 4, "type": "instruction", "headline": "Leuchten der LED", "text1": "Um nun die LED zum leuchten zu bringen wird folgender Block in die Endlosschleife eingefügt. Der Block bietet dir auszuwählen an welchen Pin die LED angeschlossen wurd und ob diese ein oder ausgeschaltet werden soll.", "xml": "" }, { + "id": 5, "type": "task", "headline": "Aufgabe 1", "text1": "Verwenden den Block zum leuchten der LED und übertrage dein erstes Programm auf die senseBox MCU.", "xml": "" } ] + }, + { + "id": 2, + "title": "WLAN einrichten", + "steps": [ + { + "id": 1, + "type": "instruction", + "headline": "Einführung", + "text1": "In diesem Tutorial lernst du wie man die senseBox mit dem Internet verbindest.", + "hardware": ["senseboxmcu"], + "requirements": [1] + }, + { + "id": 2, + "type": "instruction", + "headline": "Programmierung", + "text1": "Man benötigt folgenden Block:", + "xml": "SSIDPassword" + }, + { + "id": 3, + "type": "instruction", + "headline": "Block richtig einbinden", + "text1": "", + "xml": "SSIDPassword" + }, + { + "id": 4, + "type": "task", + "headline": "Aufgabe 1", + "text1": "Stelle eine WLAN-Verbindung mit einem beliebigen Netzwerk her.", + "xml": "SSIDPassword" + }, + { + "id": 5, + "type": "task", + "headline": "Aufgabe 2", + "text1": "Versuche das gleiche einfach nochmal. Übung macht den Meister! ;)", + "xml": "SSIDPassword" + } + ] } ] diff --git a/src/reducers/tutorialReducer.js b/src/reducers/tutorialReducer.js index f966fcc..ad80903 100644 --- a/src/reducers/tutorialReducer.js +++ b/src/reducers/tutorialReducer.js @@ -33,8 +33,6 @@ const initialStatus = () => { if(existingTutorialIds.length > 0){ status = status.filter(status => existingTutorialIds.indexOf(status.id) > -1); } - console.log('tutorial', existingTutorialIds); - console.log('tutorial', status); return status; } // window.localStorage.getItem('status') does not exist