diff --git a/public/media/hardware/breadboard.png b/public/media/hardware/breadboard.png index e6f1458..fdf294e 100644 Binary files a/public/media/hardware/breadboard.png and b/public/media/hardware/breadboard.png differ diff --git a/src/actions/tutorialActions.js b/src/actions/tutorialActions.js index c902f70..4ab1980 100644 --- a/src/actions/tutorialActions.js +++ b/src/actions/tutorialActions.js @@ -1,6 +1,6 @@ import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_STEP } from './types'; -import tutorials from '../data/tutorials.json'; +import tutorials from '../data/tutorials'; export const tutorialChange = () => (dispatch) => { dispatch({ @@ -26,10 +26,10 @@ export const tutorialCheck = (status, step) => (dispatch, getState) => { export const storeTutorialXml = (code) => (dispatch, getState) => { var id = getState().tutorial.currentId; - if(id !== null){ + if (id !== null) { var activeStep = getState().tutorial.activeStep; var steps = tutorials.filter(tutorial => tutorial.id === id)[0].steps; - if(steps[activeStep].type === 'task'){ + if (steps[activeStep].type === 'task') { var tutorialsStatus = getState().tutorial.status; var tutorialsStatusIndex = tutorialsStatus.findIndex(tutorialStatus => tutorialStatus.id === id); var tasksIndex = tutorialsStatus[tutorialsStatusIndex].tasks.findIndex(task => task.id === steps[activeStep].id); diff --git a/src/actions/tutorialBuilderActions.js b/src/actions/tutorialBuilderActions.js index 3d3effa..328c7fa 100644 --- a/src/actions/tutorialBuilderActions.js +++ b/src/actions/tutorialBuilderActions.js @@ -42,7 +42,7 @@ export const tutorialId = (id) => (dispatch) => { export const addStep = (index) => (dispatch, getState) => { var steps = getState().builder.steps; var step = { - id: index+1, + id: index + 1, type: 'instruction', headline: '', text: '' @@ -88,11 +88,11 @@ export const removeErrorStep = (index) => (dispatch, getState) => { export const changeContent = (content, index, property1, property2) => (dispatch, getState) => { var steps = getState().builder.steps; var step = steps[index]; - if(property2){ - if(step[property1] && step[property1][property2]){ + if (property2) { + if (step[property1] && step[property1][property2]) { step[property1][property2] = content; } else { - step[property1] = {[property2]: content}; + step[property1] = { [property2]: content }; } } else { step[property1] = content; @@ -107,8 +107,8 @@ export const changeContent = (content, index, property1, property2) => (dispatch export const deleteProperty = (index, property1, property2) => (dispatch, getState) => { var steps = getState().builder.steps; var step = steps[index]; - if(property2){ - if(step[property1] && step[property1][property2]){ + if (property2) { + if (step[property1] && step[property1][property2]) { delete step[property1][property2]; } } else { @@ -147,7 +147,7 @@ export const changeErrorStepIndex = (fromIndex, toIndex) => (dispatch, getState) export const setError = (index, property) => (dispatch, getState) => { var error = getState().builder.error; - if(index !== undefined){ + if (index !== undefined) { error.steps[index][property] = true; } else { @@ -162,7 +162,7 @@ export const setError = (index, property) => (dispatch, getState) => { export const deleteError = (index, property) => (dispatch, getState) => { var error = getState().builder.error; - if(index !== undefined){ + if (index !== undefined) { delete error.steps[index][property]; } else { @@ -177,44 +177,44 @@ export const deleteError = (index, property) => (dispatch, getState) => { export const setSubmitError = () => (dispatch, getState) => { var builder = getState().builder; - if(builder.id === undefined || builder.id === ''){ - dispatch(setError(undefined, 'id')); - } - if(builder.id === undefined || builder.title === ''){ + // if(builder.id === undefined || builder.id === ''){ + // dispatch(setError(undefined, 'id')); + // } + if (builder.id === undefined || builder.title === '') { dispatch(setError(undefined, 'title')); } var type = builder.steps.map((step, i) => { // media and xml are directly checked for errors in their components and // therefore do not have to be checked again - step.id = i+1; - if(i === 0){ - if(step.requirements && step.requirements.length > 0){ - var requirements = step.requirements.filter(requirement => typeof(requirement)==='number'); - if(requirements.length < step.requirements.length){ + step.id = i + 1; + if (i === 0) { + if (step.requirements && step.requirements.length > 0) { + var requirements = step.requirements.filter(requirement => typeof (requirement) === 'number'); + if (requirements.length < step.requirements.length) { dispatch(changeContent(requirements, i, 'requirements')); } } - if(step.hardware === undefined || step.hardware.length < 1){ + if (step.hardware === undefined || step.hardware.length < 1) { dispatch(setError(i, 'hardware')); } - else{ + else { var hardwareIds = data.map(hardware => hardware.id); var hardware = step.hardware.filter(hardware => hardwareIds.includes(hardware)); - if(hardware.length < step.hardware.length){ + if (hardware.length < step.hardware.length) { dispatch(changeContent(hardware, i, 'hardware')); } } } - if(step.headline === undefined || step.headline === ''){ + if (step.headline === undefined || step.headline === '') { dispatch(setError(i, 'headline')); } - if(step.text === undefined || step.text === ''){ + if (step.text === undefined || step.text === '') { dispatch(setError(i, 'text')); } return step.type; }); - if(!(type.filter(item => item === 'task').length > 0 && type.filter(item => item === 'instruction').length > 0)){ - dispatch(setError(undefined, 'type')); + if (!(type.filter(item => item === 'task').length > 0 && type.filter(item => item === 'instruction').length > 0)) { + dispatch(setError(undefined, 'type')); } }; @@ -222,11 +222,11 @@ export const setSubmitError = () => (dispatch, getState) => { export const checkError = () => (dispatch, getState) => { dispatch(setSubmitError()); var error = getState().builder.error; - if(error.id || error.title || error.type){ + if (error.id || error.title || error.type) { return true; } - for(var i = 0; i < error.steps.length; i++){ - if(Object.keys(error.steps[i]).length > 0){ + for (var i = 0; i < error.steps.length; i++) { + if (Object.keys(error.steps[i]).length > 0) { return true } } @@ -268,7 +268,7 @@ export const readJSON = (json) => (dispatch, getState) => { dispatch({ type: BUILDER_ERROR, payload: { - steps: json.steps.map(() => {return {};}) + steps: json.steps.map(() => { return {}; }) } }); // accept only valid attributes @@ -279,19 +279,19 @@ export const readJSON = (json) => (dispatch, getState) => { headline: step.headline, text: step.text }; - if(i === 0){ + if (i === 0) { object.hardware = step.hardware; object.requirements = step.requirements; } - if(step.xml){ + if (step.xml) { object.xml = step.xml; } - if(step.media && step.type === 'instruction'){ + if (step.media && step.type === 'instruction') { object.media = {}; - if(step.media.picture){ + if (step.media.picture) { object.media.picture = step.media.picture; } - else if(step.media.youtube){ + else if (step.media.youtube) { object.media.youtube = step.media.youtube; } } diff --git a/src/components/Blockly/blocks/logic.js b/src/components/Blockly/blocks/logic.js index d1e2c97..5feb5b7 100644 --- a/src/components/Blockly/blocks/logic.js +++ b/src/components/Blockly/blocks/logic.js @@ -1,5 +1,7 @@ import Blockly from 'blockly/core'; import { getColour } from '../helpers/colour'; +import * as Types from '../helpers/types'; +import { getCompatibleTypes } from '../helpers/types'; Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT @@ -230,6 +232,73 @@ Blockly.defineBlocksWithJsonArray([ // Mutator blocks. Do not extract. } ]); +Blockly.Blocks['logic_compare'] = { + /** + * Block for comparison operator. + * @this Blockly.Block + */ + init: function () { + var OPERATORS = this.RTL ? [ + ['=', 'EQ'], + ['\u2260', 'NEQ'], + ['>', 'LT'], + ['\u2265', 'LTE'], + ['<', 'GT'], + ['\u2264', 'GTE'] + ] : [ + ['=', 'EQ'], + ['\u2260', 'NEQ'], + ['<', 'LT'], + ['\u2264', 'LTE'], + ['>', 'GT'], + ['\u2265', 'GTE'] + ]; + this.setHelpUrl(Blockly.Msg.LOGIC_COMPARE_HELPURL); + this.setColour(getColour().logic); + this.setOutput(true, Types.BOOLEAN.typeName); + this.appendValueInput('A'); + this.appendValueInput('B') + .appendField(new Blockly.FieldDropdown(OPERATORS), 'OP'); + this.setInputsInline(true); + // Assign 'this' to a variable for use in the tooltip closure below. + var thisBlock = this; + this.setTooltip(function () { + var op = thisBlock.getFieldValue('OP'); + var TOOLTIPS = { + 'EQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ, + 'NEQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ, + 'LT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT, + 'LTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE, + 'GT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT, + 'GTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE + }; + return TOOLTIPS[op]; + }); + }, + /** + * Called whenever anything on the workspace changes. + * Prevent mismatched types from being compared. + * @param {!Blockly.Events.Abstract} e Change event. + * @this Blockly.Block + */ + onchange: function (e) { + var blockA = this.getInputTargetBlock('A'); + var blockB = this.getInputTargetBlock('B'); + if (blockA === null && blockB === null) { + this.getInput('A').setCheck(null); + this.getInput('B').setCheck(null); + } + if (blockA !== null && blockB === null) { + this.getInput('A').setCheck(getCompatibleTypes(blockA.outputConnection.check_[0])); + this.getInput('B').setCheck(getCompatibleTypes(blockA.outputConnection.check_[0])); + } + if (blockB !== null && blockA === null) { + this.getInput('B').setCheck(getCompatibleTypes(blockB.outputConnection.check_[0])); + this.getInput('A').setCheck(getCompatibleTypes(blockB.outputConnection.check_[0])); + } + } +}; + Blockly.Blocks['switch_case'] = { init: function () { diff --git a/src/components/Blockly/blocks/loops.js b/src/components/Blockly/blocks/loops.js index 5986b3e..63fc9aa 100644 --- a/src/components/Blockly/blocks/loops.js +++ b/src/components/Blockly/blocks/loops.js @@ -1,53 +1,211 @@ import Blockly from 'blockly'; import { getColour } from '../helpers/colour'; +import { getCompatibleTypes } from '../helpers/types' -Blockly.defineBlocksWithJsonArray([ - { - type: 'controls_for', - message0: 'count with %1 from %2 to %3 by adding %4', - args0: [ - { - type: 'field_variable', - name: 'VAR', - variable: null, - variableTypes: ['Number'], - defaultType: 'Number', - createNewVariable: true, - showOnlyVariableAssigned: false, - }, - { - type: 'input_value', - name: 'FROM', - check: 'Number', - align: 'RIGHT', - }, - { - type: 'input_value', - name: 'TO', - check: 'Number', - align: 'RIGHT', - }, - { - type: 'input_value', - name: 'BY', - check: 'Number', - align: 'RIGHT', - }, - ], - message1: '%{BKY_CONTROLS_REPEAT_INPUT_DO} %1', - args1: [ - { - type: 'input_statement', - name: 'DO', - }, - ], - inputsInline: false, - previousStatement: null, - nextStatement: null, - colour: getColour().loops, - helpUrl: '%{BKY_CONTROLS_FOR_HELPURL}', - extensions: ['contextMenu_newGetVariableBlock', 'controls_for_tooltip'], + +Blockly.Blocks['controls_whileUntil'] = { + /** + * Block for 'do while/until' loop. + * @this Blockly.Block + */ + init: function () { + var OPERATORS = + [[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_WHILE, 'WHILE'], + [Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_UNTIL, 'UNTIL']]; + this.setHelpUrl(Blockly.Msg.CONTROLS_WHILEUNTIL_HELPURL); + this.setColour(getColour().loops); + this.appendValueInput('BOOL') + .setCheck(getCompatibleTypes(Boolean)) + .appendField(new Blockly.FieldDropdown(OPERATORS), 'MODE'); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_WHILEUNTIL_INPUT_DO); + this.setPreviousStatement(true); + this.setNextStatement(true); + // Assign 'this' to a variable for use in the tooltip closure below. + var thisBlock = this; + this.setTooltip(function () { + var op = thisBlock.getFieldValue('MODE'); + var TOOLTIPS = { + 'WHILE': Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE, + 'UNTIL': Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL + }; + return TOOLTIPS[op]; + }); + } +}; + +Blockly.Blocks['controls_for'] = { + /** + * Block for 'for' loop. + * @this Blockly.Block + */ + init: function () { + this.jsonInit({ + "message0": Blockly.Msg.CONTROLS_FOR_TITLE, + "args0": [ + { + "type": "field_variable", + "name": "VAR", + "variable": null + }, + { + "type": "input_value", + "name": "FROM", + "check": getCompatibleTypes(Number), + "align": "RIGHT" + }, + { + "type": "input_value", + "name": "TO", + "check": getCompatibleTypes(Number), + "align": "RIGHT" + }, + { + "type": "input_value", + "name": "BY", + "check": getCompatibleTypes(Number), + "align": "RIGHT" + } + ], + "inputsInline": true, + "previousStatement": null, + "nextStatement": null, + "colour": getColour().loops, + "helpUrl": Blockly.Msg.CONTROLS_FOR_HELPURL + }); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_FOR_INPUT_DO); + // Assign 'this' to a variable for use in the tooltip closure below. + var thisBlock = this; + this.setTooltip(function () { + return Blockly.Msg.CONTROLS_FOR_TOOLTIP.replace('%1', + thisBlock.getFieldValue('VAR')); + }); + } +}; + +Blockly.Blocks['controls_forEach'] = { + /** + * Block for 'for each' loop. + * @this Blockly.Block + */ + init: function () { + this.jsonInit({ + "message0": Blockly.Msg.CONTROLS_FOREACH_TITLE, + "args0": [ + { + "type": "field_variable", + "name": "VAR", + "variable": null + }, + { + "type": "input_value", + "name": "LIST", + "check": getCompatibleTypes(Array) + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": getColour().loops, + "helpUrl": Blockly.Msg.CONTROLS_FOREACH_HELPURL + }); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_DO); + // Assign 'this' to a variable for use in the tooltip closure below. + var thisBlock = this; + this.setTooltip(function () { + return Blockly.Msg.CONTROLS_FOREACH_TOOLTIP.replace('%1', + thisBlock.getFieldValue('VAR')); + }); }, -]); + customContextMenu: Blockly.Blocks['controls_for'].customContextMenu, + /** @returns {!string} The type of the variable used in this block */ + getVarType: function (varName) { + return Blockly.Types.NUMBER; + } +}; + +Blockly.Blocks['controls_flow_statements'] = { + /** + * Block for flow statements: continue, break. + * @this Blockly.Block + */ + init: function () { + var OPERATORS = + [[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK, 'BREAK'], + [Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE, 'CONTINUE']]; + this.setHelpUrl(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_HELPURL); + this.setColour(getColour().loops); + this.appendDummyInput() + .appendField(new Blockly.FieldDropdown(OPERATORS), 'FLOW'); + this.setPreviousStatement(true); + // Assign 'this' to a variable for use in the tooltip closure below. + var thisBlock = this; + this.setTooltip(function () { + var op = thisBlock.getFieldValue('FLOW'); + var TOOLTIPS = { + 'BREAK': Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK, + 'CONTINUE': Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE + }; + return TOOLTIPS[op]; + }); + }, + /** + * Called whenever anything on the workspace changes. + * Add warning if this flow block is not nested inside a loop. + * @param {!Blockly.Events.Abstract} e Change event. + * @this Blockly.Block + */ + onchange: function (e) { + var legal = false; + // Is the block nested in a loop? + var block = this; + do { + if (this.LOOP_TYPES.indexOf(block.type) !== -1) { + legal = true; + break; + } + block = block.getSurroundParent(); + } while (block); + if (legal) { + this.setWarningText(null); + } else { + this.setWarningText(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING); + } + }, + /** + * List of block types that are loops and thus do not need warnings. + * To add a new loop type add this to your code: + * Blockly.Blocks['controls_flow_statements'].LOOP_TYPES.push('custom_loop'); + */ + LOOP_TYPES: ['controls_repeat', 'controls_repeat_ext', 'controls_forEach', + 'controls_for', 'controls_whileUntil'] +}; + +Blockly.Blocks['controls_repeat_ext'] = { + /** + * Block for repeat n times (external number). + * @this Blockly.Block + */ + init: function () { + this.jsonInit({ + "message0": Blockly.Msg.CONTROLS_REPEAT_TITLE, + "args0": [ + { + "type": "input_value", + "name": "TIMES", + "check": getCompatibleTypes(Number), + } + ], + "previousStatement": null, + "nextStatement": null, + "colour": getColour().loops, + "tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP, + "helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL + }); + this.appendStatementInput('DO') + .appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO); + } +}; diff --git a/src/components/Blockly/blocks/sensebox-sensors.js b/src/components/Blockly/blocks/sensebox-sensors.js index 2798249..2465b24 100644 --- a/src/components/Blockly/blocks/sensebox-sensors.js +++ b/src/components/Blockly/blocks/sensebox-sensors.js @@ -301,3 +301,23 @@ Blockly.Blocks['sensebox_button'] = { }; +/** + * SCD30 CO2 Sensor + * + */ + + +Blockly.Blocks['sensebox_scd30'] = { + init: function () { + var dropdownOptions = [[Blockly.Msg.senseBox_temp, "temperature"], [Blockly.Msg.senseBox_hum, "humidity"], [Blockly.Msg.senseBox_bme_co2, "CO2"]]; + this.appendDummyInput() + .appendField(Blockly.Msg.senseBox_scd30); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_RIGHT) + .appendField(Blockly.Msg.senseBox_value) + .appendField(new Blockly.FieldDropdown(dropdownOptions), "dropdown") + this.setOutput(true, Types.NUMBER.typeName); + this.setColour(getColour().sensebox); + this.setTooltip(Blockly.Msg.senseBox_bme_tip); + } +}; diff --git a/src/components/Blockly/blocks/text.js b/src/components/Blockly/blocks/text.js index 8b13789..f5c234b 100644 --- a/src/components/Blockly/blocks/text.js +++ b/src/components/Blockly/blocks/text.js @@ -1 +1,202 @@ +import Blockly from 'blockly/core'; +import { getColour } from '../helpers/colour'; +import * as Types from '../helpers/types'; + + +Blockly.defineBlocksWithJsonArray([ // BEGIN JSON EXTRACT + // Block for text value + { + "type": "text", + "message0": "%1", + "args0": [{ + "type": "field_input", + "name": "TEXT", + "text": "" + }], + "output": Types.TEXT.typeName, + "style": "text_blocks", + "helpUrl": "%{BKY_TEXT_TEXT_HELPURL}", + "tooltip": "%{BKY_TEXT_TEXT_TOOLTIP}", + "extensions": [ + "text_quotes", + "parent_tooltip_when_inline" + ] + }, + { + "type": "text_multiline", + "message0": "%1 %2", + "args0": [{ + "type": "field_image", + "src": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAARCAYAAADpP' + + 'U2iAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAdhgAAHYYBXaITgQAAABh0RVh0' + + 'U29mdHdhcmUAcGFpbnQubmV0IDQuMS42/U4J6AAAAP1JREFUOE+Vks0KQUEYhjm' + + 'RIja4ABtZ2dm5A3t3Ia6AUm7CylYuQRaUhZSlLZJiQbFAyRnPN33y01HOW08z88' + + '73zpwzM4F3GWOCruvGIE4/rLaV+Nq1hVGMBqzhqlxgCys4wJA65xnogMHsQ5luj' + + 'nYHTejBBCK2mE4abjCgMGhNxHgDFWjDSG07kdfVa2pZMf4ZyMAdWmpZMfYOsLiD' + + 'MYMjlMB+K613QISRhTnITnsYg5yUd0DETmEoMlkFOeIT/A58iyK5E18BuTBfgYX' + + 'fwNJv4P9/oEBerLylOnRhygmGdPpTTBZAPkde61lbQe4moWUvYUZYLfUNftIY4z' + + 'wA5X2Z9AYnQrEAAAAASUVORK5CYII=', + "width": 12, + "height": 17, + "alt": '\u00B6' + }, { + "type": "field_multilinetext", + "name": "TEXT", + "text": "" + }], + "output": Types.TEXT.typeName, + "style": "text_blocks", + "helpUrl": "%{BKY_TEXT_TEXT_HELPURL}", + "tooltip": "%{BKY_TEXT_TEXT_TOOLTIP}", + "extensions": [ + "parent_tooltip_when_inline" + ] + }, + { + "type": "text_join", + "message0": "", + "output": Types.TEXT.typeName, + "style": "text_blocks", + "helpUrl": "%{BKY_TEXT_JOIN_HELPURL}", + "tooltip": "%{BKY_TEXT_JOIN_TOOLTIP}", + "mutator": "text_join_mutator" + + }, + { + "type": "text_create_join_container", + "message0": "%{BKY_TEXT_CREATE_JOIN_TITLE_JOIN} %1 %2", + "args0": [{ + "type": "input_dummy" + }, + { + "type": "input_statement", + "name": "STACK" + }], + "style": "text_blocks", + "tooltip": "%{BKY_TEXT_CREATE_JOIN_TOOLTIP}", + "enableContextMenu": false + }, + { + "type": "text_create_join_item", + "message0": "%{BKY_TEXT_CREATE_JOIN_ITEM_TITLE_ITEM}", + "previousStatement": null, + "nextStatement": null, + "style": getColour().text, + "tooltip": "%{BKY_TEXT_CREATE_JOIN_ITEM_TOOLTIP}", + "enableContextMenu": false + }, + { + "type": "text_append", + "message0": "%{BKY_TEXT_APPEND_TITLE}", + "args0": [{ + "type": "field_variable", + "name": "VAR", + "variable": "%{BKY_TEXT_APPEND_VARIABLE}" + }, + { + "type": "input_value", + "name": "TEXT" + }], + "previousStatement": null, + "nextStatement": null, + "style": "text_blocks", + "extensions": [ + "text_append_tooltip" + ] + }, + { + "type": "text_length", + "message0": "%{BKY_TEXT_LENGTH_TITLE}", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": ['String', 'Array'] + } + ], + "output": Types.NUMBER.typeName, + "style": "text_blocks", + "tooltip": "%{BKY_TEXT_LENGTH_TOOLTIP}", + "helpUrl": "%{BKY_TEXT_LENGTH_HELPURL}" + }, + { + "type": "text_isEmpty", + "message0": "%{BKY_TEXT_ISEMPTY_TITLE}", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": ['String', 'Array'] + } + ], + "output": Types.BOOLEAN.typeName, + "style": "text_blocks", + "tooltip": "%{BKY_TEXT_ISEMPTY_TOOLTIP}", + "helpUrl": "%{BKY_TEXT_ISEMPTY_HELPURL}" + }, + { + "type": "text_indexOf", + "message0": "%{BKY_TEXT_INDEXOF_TITLE}", + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": "String" + }, + { + "type": "field_dropdown", + "name": "END", + "options": [ + [ + "%{BKY_TEXT_INDEXOF_OPERATOR_FIRST}", + "FIRST" + ], + [ + "%{BKY_TEXT_INDEXOF_OPERATOR_LAST}", + "LAST" + ] + ] + }, + { + "type": "input_value", + "name": "FIND", + "check": "String" + } + ], + "output": Types.NUMBER.typeName, + "style": "text_blocks", + "helpUrl": "%{BKY_TEXT_INDEXOF_HELPURL}", + "inputsInline": true, + "extensions": [ + "text_indexOf_tooltip" + ] + }, + { + "type": "text_charAt", + "message0": "%{BKY_TEXT_CHARAT_TITLE}", // "in text %1 %2" + "args0": [ + { + "type": "input_value", + "name": "VALUE", + "check": "String" + }, + { + "type": "field_dropdown", + "name": "WHERE", + "options": [ + ["%{BKY_TEXT_CHARAT_FROM_START}", "FROM_START"], + ["%{BKY_TEXT_CHARAT_FROM_END}", "FROM_END"], + ["%{BKY_TEXT_CHARAT_FIRST}", "FIRST"], + ["%{BKY_TEXT_CHARAT_LAST}", "LAST"], + ["%{BKY_TEXT_CHARAT_RANDOM}", "RANDOM"] + ] + } + ], + "output": Types.TEXT.typeName, + "style": "text_blocks", + "helpUrl": "%{BKY_TEXT_CHARAT_HELPURL}", + "inputsInline": true, + "mutator": "text_charAt_mutator" + } +]); // END JSON EXTRACT (Do not delete this comment.) \ No newline at end of file diff --git a/src/components/Blockly/generator/sensebox-sensors.js b/src/components/Blockly/generator/sensebox-sensors.js index ed8bfe3..6599e4a 100644 --- a/src/components/Blockly/generator/sensebox-sensors.js +++ b/src/components/Blockly/generator/sensebox-sensors.js @@ -251,4 +251,48 @@ Blockly.Arduino.sensebox_button = function () { code = 'button_' + dropdown_pin + '.wasPressed()'; } return [code, Blockly.Arduino.ORDER_ATOMIC]; -}; \ No newline at end of file +}; + +/** + * SCD30 CO2 Sensor + * + */ + +Blockly.Arduino.sensebox_scd30 = function () { + var dropdown = this.getFieldValue('dropdown'); + Blockly.Arduino.libraries_['scd30_library'] = '#include "SparkFun_SCD30_Arduino_Library.h"' + Blockly.Arduino.libraries_['library_senseBoxMCU'] = '#include "SenseBoxMCU.h"'; + Blockly.Arduino.definitions_['SCD30'] = 'SCD30 airSensor;'; + Blockly.Arduino.variables_['scd30_temp'] = 'float scd30_temp;'; + Blockly.Arduino.variables_['scd30_humi'] = 'float scd30_humi;'; + Blockly.Arduino.variables_['scd30_co2'] = 'float scd30_co2;'; + Blockly.Arduino.setupCode_['init_scd30'] = ` Wire.begin(); + if (airSensor.begin() == false) + { + Serial.println("Air sensor not detected. Please check wiring. Freezing..."); + while (1) + ; + }`; + Blockly.Arduino.loopCodeOnce_['scd30_getData'] = `if (airSensor.dataAvailable()) + { + scd30_co2 = airSensor.getCO2(); + scd30_temp = airSensor.getTemperature(); + scd30_humi = airSensor.getHumidity(); + }` + var code = ''; + switch (dropdown) { + case 'temperature': + code = 'scd30_temp'; + break; + case 'humidity': + code = 'scd30_humi'; + break; + case 'CO2': + code = 'scd30_co2'; + break; + default: + code = '' + } + return [code, Blockly.Arduino.ORDER_ATOMIC]; + +} \ No newline at end of file diff --git a/src/components/Blockly/helpers/types.js b/src/components/Blockly/helpers/types.js index 88c9348..48def90 100644 --- a/src/components/Blockly/helpers/types.js +++ b/src/components/Blockly/helpers/types.js @@ -18,6 +18,7 @@ export const CHARACTER = { export const BOOLEAN = { typeId: 'Boolean', + typeName: 'Boolean', typeMsgName: 'ARD_TYPE_BOOL', } @@ -89,7 +90,7 @@ const compatibleTypes = { boolean: ['boolean'], int: ['int'], char: ['char'], - string: ['String'], + String: ['String'], void: ['void'], long: ['int', 'long'], double: ['int', 'long', 'double'], diff --git a/src/components/Blockly/msg/de.js b/src/components/Blockly/msg/de.js index 159693e..cf6db85 100644 --- a/src/components/Blockly/msg/de.js +++ b/src/components/Blockly/msg/de.js @@ -768,3 +768,5 @@ Blockly.Msg.senseBox_telegram_do_on_message = "bei Nachricht" Blockly.Msg.senseBox_telegram_message = "Nachricht" Blockly.Msg.senseBox_telegram_send = "Sende Nachricht" +Blockly.Msg.senseBox_scd30 = "CO2 Sensor (Sensirion SCD30)"; + diff --git a/src/components/Blockly/msg/en.js b/src/components/Blockly/msg/en.js index b4bc8c0..e32e321 100644 --- a/src/components/Blockly/msg/en.js +++ b/src/components/Blockly/msg/en.js @@ -750,3 +750,4 @@ Blockly.Msg.sensebox_sd_filename = "data"; Blockly.Msg.sensebox_soil_smt50 = "Soil Moisture and Temperature (SMT50)"; Blockly.Msg.sensebox_web_readHTML_filename = "File:"; +Blockly.Msg.senseBox_scd30 = "CO2 Sensor (Sensirion SCD30)"; \ No newline at end of file diff --git a/src/components/Blockly/toolbox/Toolbox.js b/src/components/Blockly/toolbox/Toolbox.js index 627f961..3cfae6e 100644 --- a/src/components/Blockly/toolbox/Toolbox.js +++ b/src/components/Blockly/toolbox/Toolbox.js @@ -5,7 +5,6 @@ import '@blockly/block-plus-minus'; import { TypedVariableModal } from '@blockly/plugin-typed-variable-modal'; import * as Blockly from 'blockly/core'; -import BlocklyComponent from '../BlocklyComponent'; @@ -45,6 +44,7 @@ class Toolbox extends React.Component { + diff --git a/src/components/Compile.js b/src/components/Compile.js index c9ce34b..946466f 100644 --- a/src/components/Compile.js +++ b/src/components/Compile.js @@ -38,7 +38,7 @@ const styles = (theme) => ({ class Compile extends Component { - constructor(props){ + constructor(props) { super(props); this.state = { progress: false, @@ -50,9 +50,9 @@ class Compile extends Component { }; } - componentDidUpdate(props){ - if(props.name !== this.props.name){ - this.setState({name: this.props.name}); + componentDidUpdate(props) { + if (props.name !== this.props.name) { + this.setState({ name: this.props.name }); } } @@ -68,17 +68,17 @@ class Compile extends Component { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) - .then(response => response.json()) - .then(data => { - console.log(data); - this.setState({id: data.data.id}, () => { - this.createFileName(); + .then(response => response.json()) + .then(data => { + console.log(data); + this.setState({ id: data.data.id }, () => { + this.createFileName(); + }); + }) + .catch(err => { + console.log(err); + this.setState({ progress: false, file: false, open: true, title: 'Fehler', content: 'Etwas ist beim Kompilieren schief gelaufen. Versuche es nochmal.' }); }); - }) - .catch(err => { - console.log(err); - this.setState({ progress: false, file: false, open: true, title: 'Fehler', content: 'Etwas ist beim Kompilieren schief gelaufen. Versuche es nochmal.' }); - }); } download = () => { @@ -95,33 +95,33 @@ class Compile extends Component { } createFileName = () => { - if(this.state.name){ + if (this.state.name) { this.download(); } - else{ + else { this.setState({ file: true, open: true, title: 'Blöcke kompilieren', content: 'Bitte gib einen Namen für die Bennenung des zu kompilierenden Programms ein und bestätige diesen mit einem Klick auf \'Eingabe\'.' }); } } setFileName = (e) => { - this.setState({name: e.target.value}); + this.setState({ name: e.target.value }); } render() { return (
{this.props.iconButton ? - + this.compile()} > - + - : + : } @@ -132,15 +132,15 @@ class Compile extends Component { title={this.state.title} content={this.state.content} onClose={this.toggleDialog} - onClick={this.state.file ? () => {this.toggleDialog(); this.setState({name: this.props.name})} : this.toggleDialog} + onClick={this.state.file ? () => { this.toggleDialog(); this.setState({ name: this.props.name }) } : this.toggleDialog} button={this.state.file ? 'Abbrechen' : 'Schließen'} > {this.state.file ? -
- +
+
- : null} + : null}
); @@ -159,4 +159,4 @@ const mapStateToProps = state => ({ }); -export default connect(mapStateToProps, { workspaceName })(withStyles(styles, {withTheme: true})(Compile)); +export default connect(mapStateToProps, { workspaceName })(withStyles(styles, { withTheme: true })(Compile)); diff --git a/src/components/Tutorial/Builder/Builder.js b/src/components/Tutorial/Builder/Builder.js index 2a177f2..cca4296 100644 --- a/src/components/Tutorial/Builder/Builder.js +++ b/src/components/Tutorial/Builder/Builder.js @@ -8,7 +8,6 @@ import { saveAs } from 'file-saver'; import { detectWhitespacesAndReturnReadableResult } from '../../../helpers/whitespace'; import Breadcrumbs from '../../Breadcrumbs'; -import Id from './Id'; import Textfield from './Textfield'; import Step from './Step'; import Dialog from '../../Dialog'; @@ -33,7 +32,7 @@ const styles = (theme) => ({ class Builder extends Component { - constructor(props){ + constructor(props) { super(props); this.state = { open: false, @@ -47,26 +46,33 @@ class Builder extends Component { this.inputRef = React.createRef(); } - componentWillUnmount(){ + componentWillUnmount() { this.reset(); } submit = () => { + console.log(this.props.id) + if (this.props.id === null) { + var randomID = Date.now(); + } else { + randomID = this.props.id; + } + var isError = this.props.checkError(); - if(isError){ - this.setState({ snackbar: true, key: Date.now(), message: `Die Angaben für das Tutorial sind nicht vollständig.`, type: 'error'}); + if (isError) { + this.setState({ snackbar: true, key: Date.now(), message: `Die Angaben für das Tutorial sind nicht vollständig.`, type: 'error' }); window.scrollTo(0, 0); } - else{ + else { // export steps without attribute 'url' var steps = this.props.steps.map(step => { - if(step.url){ + if (step.url) { delete step.url; } return step; }); var tutorial = { - id: this.props.id, + id: randomID, title: this.props.title, steps: steps } @@ -77,15 +83,15 @@ class Builder extends Component { reset = () => { this.props.resetTutorial(); - this.setState({ snackbar: true, key: Date.now(), message: `Das Tutorial wurde erfolgreich zurückgesetzt.`, type: 'success'}); + this.setState({ snackbar: true, key: Date.now(), message: `Das Tutorial wurde erfolgreich zurückgesetzt.`, type: 'success' }); window.scrollTo(0, 0); } uploadJsonFile = (jsonFile) => { this.props.progress(true); - if(jsonFile.type !== 'application/json'){ + if (jsonFile.type !== 'application/json') { this.props.progress(false); - this.setState({ open: true, string: false, title: 'Unzulässiger Dateityp', content: 'Die übergebene Datei entspricht nicht dem geforderten Format. Es sind nur JSON-Dateien zulässig.'}); + this.setState({ open: true, string: false, title: 'Unzulässiger Dateityp', content: 'Die übergebene Datei entspricht nicht dem geforderten Format. Es sind nur JSON-Dateien zulässig.' }); } else { var reader = new FileReader(); @@ -97,27 +103,27 @@ class Builder extends Component { } uploadJsonString = () => { - this.setState({ open: true, string: true, title: 'JSON-String einfügen', content: ''}); + this.setState({ open: true, string: true, title: 'JSON-String einfügen', content: '' }); } readJson = (jsonString, isFile) => { try { var result = JSON.parse(jsonString); - if(!this.checkSteps(result.steps)){ + if (!this.checkSteps(result.steps)) { result.steps = [{}]; } this.props.readJSON(result); - this.setState({ snackbar: true, key: Date.now(), message: `${isFile ? 'Die übergebene JSON-Datei' : 'Der übergebene JSON-String'} wurde erfolgreich übernommen.`, type: 'success'}); - } catch(err){ + this.setState({ snackbar: true, key: Date.now(), message: `${isFile ? 'Die übergebene JSON-Datei' : 'Der übergebene JSON-String'} wurde erfolgreich übernommen.`, type: 'success' }); + } catch (err) { console.log(err); this.props.progress(false); this.props.jsonString(''); - this.setState({ open: true, string: false, title: 'Ungültiges JSON-Format', content: `${isFile ? 'Die übergebene Datei' : 'Der übergebene String'} enthält nicht valides JSON. Bitte überprüfe ${isFile ? 'die JSON-Datei' : 'den JSON-String'} und versuche es erneut.`}); + this.setState({ open: true, string: false, title: 'Ungültiges JSON-Format', content: `${isFile ? 'Die übergebene Datei' : 'Der übergebene String'} enthält nicht valides JSON. Bitte überprüfe ${isFile ? 'die JSON-Datei' : 'den JSON-String'} und versuche es erneut.` }); } } checkSteps = (steps) => { - if(!(steps && steps.length > 0)){ + if (!(steps && steps.length > 0)) { return false; } return true; @@ -131,41 +137,41 @@ class Builder extends Component { render() { return (
- +

Tutorial-Builder

{/*upload JSON*/}
{this.uploadJsonFile(e.target.files[0])}} + onChange={(e) => { this.uploadJsonFile(e.target.files[0]) }} id="open-json" type="file" /> - +
- + {/*Tutorial-Builder-Form*/} {this.props.error.type ? - {`Ein Tutorial muss mindestens jeweils eine Instruktion und eine Aufgabe enthalten.`} - : null} - - + {`Ein Tutorial muss mindestens jeweils eine Instruktion und eine Aufgabe enthalten.`} + : null} + {/* */} + {this.props.steps.map((step, i) => - + )} {/*submit or reset*/} - - - + + + @@ -182,16 +188,16 @@ class Builder extends Component { button={'Schließen'} actions={ this.state.string ? -
- - -
- : null +
+ + +
+ : null } > {this.state.string ? - - : null} + + : null} ({ isProgress: state.builder.progress }); -export default connect(mapStateToProps, { checkError, readJSON, jsonString, progress, resetTutorial })(withStyles(styles, {withTheme: true})(Builder)); +export default connect(mapStateToProps, { checkError, readJSON, jsonString, progress, resetTutorial })(withStyles(styles, { withTheme: true })(Builder)); diff --git a/src/components/Tutorial/Builder/Hardware.js b/src/components/Tutorial/Builder/Hardware.js index 770cc8c..b22b0d0 100644 --- a/src/components/Tutorial/Builder/Hardware.js +++ b/src/components/Tutorial/Builder/Hardware.js @@ -5,7 +5,6 @@ import { changeContent, setError, deleteError } from '../../../actions/tutorialB import hardware from '../../../data/hardware.json'; -import { fade } from '@material-ui/core/styles/colorManipulator'; import { withStyles } from '@material-ui/core/styles'; import withWidth, { isWidthDown } from '@material-ui/core/withWidth'; import GridList from '@material-ui/core/GridList'; @@ -16,7 +15,8 @@ import FormLabel from '@material-ui/core/FormLabel'; const styles = theme => ({ multiGridListTile: { - background: fade(theme.palette.secondary.main, 0.5), + background: "#4EAF47", + opacity: 0.9, height: '30px' }, multiGridListTileTitle: { @@ -47,17 +47,17 @@ class Requirements extends Component { onChange = (hardware) => { var hardwareArray = this.props.value; - if(hardwareArray.filter(value => value === hardware).length > 0){ + if (hardwareArray.filter(value => value === hardware).length > 0) { hardwareArray = hardwareArray.filter(value => value !== hardware); } else { hardwareArray.push(hardware); - if(this.props.error){ + if (this.props.error) { this.props.deleteError(this.props.index, 'hardware'); } } this.props.changeContent(hardwareArray, this.props.index, 'hardware'); - if(hardwareArray.length === 0){ + if (hardwareArray.length === 0) { this.props.setError(this.props.index, 'hardware'); } } @@ -65,26 +65,26 @@ class Requirements extends Component { render() { var cols = isWidthDown('md', this.props.width) ? isWidthDown('sm', this.props.width) ? isWidthDown('xs', this.props.width) ? 2 : 3 : 4 : 6; return ( -
- Hardware - Beachte, dass die Reihenfolge des Auswählens maßgebend ist. +
+ Hardware + Beachte, dass die Reihenfolge des Auswählens maßgebend ist. {this.props.error ? Wähle mindestens eine Hardware-Komponente aus. : null} - {hardware.map((picture,i) => ( - this.onChange(picture.id)} classes={{tile: this.props.value.filter(value => value === picture.id).length > 0 ? this.props.classes.active : this.props.classes.border}}> -
- {picture.name} -
- - {picture.name} -
- } - /> - - ))} + {hardware.map((picture, i) => ( + this.onChange(picture.id)} classes={{ tile: this.props.value.filter(value => value === picture.id).length > 0 ? this.props.classes.active : this.props.classes.border }}> +
+ {picture.name} +
+ + {picture.name} +
+ } + /> + + ))}
); diff --git a/src/components/Tutorial/Builder/Id.js b/src/components/Tutorial/Builder/Id.js index ad18dc2..b3179ac 100644 --- a/src/components/Tutorial/Builder/Id.js +++ b/src/components/Tutorial/Builder/Id.js @@ -28,81 +28,83 @@ const styles = theme => ({ class Id extends Component { handleChange = (e) => { - var value = parseInt(e.target.value); - if(Number.isInteger(value) && value > 0){ + var value = + + parseInt(e.target.value); + if (Number.isInteger(value) && value > 0) { this.props.tutorialId(value); - if(this.props.error){ + if (this.props.error) { this.props.deleteError(undefined, 'id'); } } else { this.props.tutorialId(value.toString()); - this.props.setError(undefined,'id'); + this.props.setError(undefined, 'id'); } }; handleCounter = (step) => { - if(this.props.value+step < 1){ - this.props.setError(undefined,'id'); + if (this.props.value + step < 1) { + this.props.setError(undefined, 'id'); } - else if(this.props.error){ + else if (this.props.error) { this.props.deleteError(undefined, 'id'); } - if(!this.props.value || !Number.isInteger(this.props.value)){ - this.props.tutorialId(0+step); + if (!this.props.value || !Number.isInteger(this.props.value)) { + this.props.tutorialId(0 + step); } else { - this.props.tutorialId(this.props.value+step); + this.props.tutorialId(this.props.value + step); } } render() { return ( -
- - - ID +
+ + + ID - - - -
- } - /> - {this.props.error ? Gib eine positive ganzzahlige Zahl ein. : null} -
- Beachte, dass die ID eindeutig sein muss. Sie muss sich also zu den anderen Tutorials unterscheiden. + + + +
+ } + /> + {this.props.error ? Gib eine positive ganzzahlige Zahl ein. : null} + + Beachte, dass die ID eindeutig sein muss. Sie muss sich also zu den anderen Tutorials unterscheiden.
); }; diff --git a/src/components/Tutorial/Builder/Requirements.js b/src/components/Tutorial/Builder/Requirements.js index 2867325..8be6bf4 100644 --- a/src/components/Tutorial/Builder/Requirements.js +++ b/src/components/Tutorial/Builder/Requirements.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { changeContent } from '../../../actions/tutorialBuilderActions'; -import tutorials from '../../../data/tutorials.json'; +import tutorials from '../../../data/tutorials'; import FormGroup from '@material-ui/core/FormGroup'; import Checkbox from '@material-ui/core/Checkbox'; @@ -17,7 +17,7 @@ class Requirements extends Component { onChange = (e) => { var requirements = this.props.value; var value = parseInt(e.target.value) - if(e.target.checked){ + if (e.target.checked) { requirements.push(value); } else { @@ -28,9 +28,9 @@ class Requirements extends Component { render() { return ( - - Voraussetzungen - Beachte, dass die Reihenfolge des Anhakens maßgebend ist. + + Voraussetzungen + Beachte, dass die Reihenfolge des Anhakens maßgebend ist. {tutorials.map((tutorial, i) => ({ color: theme.palette.text.primary }, multiGridListTile: { - background: fade(theme.palette.secondary.main, 0.5), + background: "#4EAF47", + opacity: 0.9, height: '30px' }, multiGridListTileTitle: { @@ -45,46 +45,47 @@ class Hardware extends Component { }; handleClickOpen = (hardwareInfo) => { - this.setState({open: true, hardwareInfo}); + this.setState({ open: true, hardwareInfo }); }; handleClose = () => { - this.setState({open: false, hardwareInfo: {}}); + this.setState({ open: false, hardwareInfo: {} }); }; render() { var cols = isWidthDown('md', this.props.width) ? isWidthDown('sm', this.props.width) ? isWidthDown('xs', this.props.width) ? 2 : 3 : 4 : 6; return ( -
+
Für die Umsetzung benötigst du folgende Hardware: - {this.props.picture.map((picture,i) => { - var hardwareInfo = hardware.filter(hardware => hardware.id === picture)[0]; - return( - -
- {hardwareInfo.name} this.handleClickOpen(hardwareInfo)}/> -
- - {hardwareInfo.name} -
- } - actionIcon={ - this.handleClickOpen(hardwareInfo)}> - - - } - /> - - )})} + {this.props.picture.map((picture, i) => { + var hardwareInfo = hardware.filter(hardware => hardware.id === picture)[0]; + return ( + +
+ {hardwareInfo.name} this.handleClickOpen(hardwareInfo)} /> +
+ + {hardwareInfo.name} +
+ } + actionIcon={ + this.handleClickOpen(hardwareInfo)}> + + + } + /> + + ) + })}
- {this.state.hardwareInfo.name}/ + {this.state.hardwareInfo.name} Weitere Informationen zur Hardware-Komponente findest du hier.
diff --git a/src/components/Tutorial/Requirement.js b/src/components/Tutorial/Requirement.js index 9c2542e..1ebd342 100644 --- a/src/components/Tutorial/Requirement.js +++ b/src/components/Tutorial/Requirement.js @@ -5,7 +5,7 @@ import { connect } from 'react-redux'; import clsx from 'clsx'; import { withRouter, Link } from 'react-router-dom'; -import tutorials from '../../data/tutorials.json'; +import tutorials from '../../data/tutorials'; import { fade } from '@material-ui/core/styles/colorManipulator'; import { withStyles } from '@material-ui/core/styles'; @@ -63,46 +63,47 @@ class Requirement extends Component { render() { var tutorialIds = this.props.tutorialIds; return ( -
- Es bietet sich an folgende Tutorials vorab erfolgreich gelöst zu haben: +
+ Bevor du mit diesem Tutorial fortfährst solltest du folgende Tutorials erfolgreich abgeschlossen haben: {tutorialIds.map((tutorialId, i) => { var title = tutorials.filter(tutorial => tutorial.id === tutorialId)[0].title; var status = this.props.status.filter(status => status.id === tutorialId)[0]; var tasks = status.tasks; var error = status.tasks.filter(task => task.type === 'error').length > 0; - var success = status.tasks.filter(task => task.type === 'success').length/tasks.length + var success = status.tasks.filter(task => task.type === 'success').length / tasks.length var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other'; - return( + return ( - +
-
- +
+ {error || success === 1 ? - : } + : } {success < 1 && !error ? - + - : null} + : null}
{error || success === 1 ? - - : 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success*100)}% + + : 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success * 100)}% }
-
- {title} +
+ {title}
- )} + ) + } )}
@@ -120,4 +121,4 @@ const mapStateToProps = state => ({ status: state.tutorial.status }); -export default connect(mapStateToProps, null)(withStyles(styles, {withTheme: true})(withRouter(Requirement))); +export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(withRouter(Requirement))); diff --git a/src/components/Tutorial/SolutionCheck.js b/src/components/Tutorial/SolutionCheck.js index bc8c6c8..92c093f 100644 --- a/src/components/Tutorial/SolutionCheck.js +++ b/src/components/Tutorial/SolutionCheck.js @@ -8,7 +8,7 @@ import { withRouter } from 'react-router-dom'; import Compile from '../Compile'; import Dialog from '../Dialog'; -import tutorials from '../../data/tutorials.json'; +import tutorials from '../../data/tutorials'; import { checkXml } from '../../helpers/compareXml'; import { withStyles } from '@material-ui/core/styles'; @@ -32,16 +32,16 @@ const styles = (theme) => ({ class SolutionCheck extends Component { - state={ + state = { open: false, msg: '' } toggleDialog = () => { - if(this.state.open){ + if (this.state.open) { this.setState({ open: false, msg: '' }); } - else{ + else { this.setState({ open: !this.state }); } } @@ -61,15 +61,15 @@ class SolutionCheck extends Component { this.check()} > - + {this.state.msg.type === 'success' ? -
+
- {this.props.activeStep === steps.length-1 ? + {this.props.activeStep === steps.length - 1 ? - : + : }
- : null} + : null}
@@ -125,4 +125,4 @@ const mapStateToProps = state => ({ xml: state.workspace.code.xml }); -export default connect(mapStateToProps, { tutorialCheck, tutorialStep })(withStyles(styles, {withTheme: true})(withRouter(SolutionCheck))); +export default connect(mapStateToProps, { tutorialCheck, tutorialStep })(withStyles(styles, { withTheme: true })(withRouter(SolutionCheck))); diff --git a/src/components/Tutorial/StepperHorizontal.js b/src/components/Tutorial/StepperHorizontal.js index c446675..b4d87c0 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 '../../data/tutorials.json'; +import tutorials from '../../data/tutorials'; import { fade } from '@material-ui/core/styles/colorManipulator'; import { withStyles } from '@material-ui/core/styles'; @@ -58,31 +58,31 @@ class StepperHorizontal extends Component { var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other'; var title = tutorials.filter(tutorial => tutorial.id === tutorialId)[0].title; return ( -
+
{error || success > 0 ? -
+
- : null} + : null} {success < 1 && !error ? -
+
- : null} + : null}
- +
- {tutorialStatus !== 'Other' ?
: null} - {title} + {tutorialStatus !== 'Other' ?
: null} + {title}
@@ -104,4 +104,4 @@ const mapStateToProps = state => ({ currentTutorialId: state.tutorial.currentId }); -export default connect(mapStateToProps, null)(withRouter(withStyles(styles, {withTheme: true})(StepperHorizontal))); +export default connect(mapStateToProps, null)(withRouter(withStyles(styles, { withTheme: true })(StepperHorizontal))); diff --git a/src/components/Tutorial/Tutorial.js b/src/components/Tutorial/Tutorial.js index f2a7bb7..09c8e27 100644 --- a/src/components/Tutorial/Tutorial.js +++ b/src/components/Tutorial/Tutorial.js @@ -13,24 +13,24 @@ import NotFound from '../NotFound'; import { detectWhitespacesAndReturnReadableResult } from '../../helpers/whitespace'; -import tutorials from '../../data/tutorials.json'; +import tutorials from '../../data/tutorials'; import Card from '@material-ui/core/Card'; import Button from '@material-ui/core/Button'; class Tutorial extends Component { - componentDidMount(){ + componentDidMount() { this.props.tutorialId(Number(this.props.match.params.tutorialId)); } - componentDidUpdate(props, state){ - if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){ + componentDidUpdate(props, state) { + if (props.currentTutorialId !== Number(this.props.match.params.tutorialId)) { this.props.tutorialId(Number(this.props.match.params.tutorialId)); } } - componentWillUnmount(){ + componentWillUnmount() { this.props.tutorialId(null); this.props.workspaceName(null); } @@ -43,30 +43,30 @@ class Tutorial extends Component { var name = step ? `${detectWhitespacesAndReturnReadableResult(tutorial.title)}_${detectWhitespacesAndReturnReadableResult(step.headline)}` : null; return ( !Number.isInteger(currentTutorialId) || currentTutorialId < 1 || !tutorial ? - - : -
- + + : +
+ - + -
- - {/* calc(Card-padding: 10px + Button-height: 35px + Button-marginTop: 15px)*/} - - {step ? - step.type === 'instruction' ? - - : // if step.type === 'assessment' - : null} +
+ + {/* calc(Card-padding: 10px + Button-height: 35px + Button-marginTop: 15px)*/} + + {step ? + step.type === 'instruction' ? + + : // if step.type === 'assessment' + : null} -
- - -
-
+
+ + +
+ +
-
); }; } diff --git a/src/components/Tutorial/TutorialHome.js b/src/components/Tutorial/TutorialHome.js index e56cf31..a6ffa1a 100644 --- a/src/components/Tutorial/TutorialHome.js +++ b/src/components/Tutorial/TutorialHome.js @@ -6,7 +6,8 @@ import clsx from 'clsx'; import Breadcrumbs from '../Breadcrumbs'; -import tutorials from '../../data/tutorials.json'; +import tutorials from '../../data/tutorials'; +// import tutorials from '../../data/tutorials.json'; import { Link } from 'react-router-dom'; @@ -54,7 +55,7 @@ class TutorialHome extends Component { render() { return (
- +

Tutorial-Übersicht

@@ -62,36 +63,37 @@ class TutorialHome extends Component { var status = this.props.status.filter(status => status.id === tutorial.id)[0]; var tasks = status.tasks; var error = status.tasks.filter(task => task.type === 'error').length > 0; - var success = status.tasks.filter(task => task.type === 'success').length/tasks.length + var success = status.tasks.filter(task => task.type === 'success').length / tasks.length var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other'; return ( - - + + {tutorial.title} -
- +
+ {error || success === 1 ? - - : } + + : } {success < 1 && !error ? - + - : null} + : null}
{error || success === 1 ? - - : 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success*100)}% + + : 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success * 100)}% }
- )})} + ) + })}
); @@ -108,4 +110,4 @@ const mapStateToProps = state => ({ status: state.tutorial.status }); -export default connect(mapStateToProps, null)(withStyles(styles, {withTheme: true})(TutorialHome)); +export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(TutorialHome)); diff --git a/src/data/AnzeigeVonMesswertenAufDemDisplay.json b/src/data/AnzeigeVonMesswertenAufDemDisplay.json new file mode 100644 index 0000000..4d9bbdd --- /dev/null +++ b/src/data/AnzeigeVonMesswertenAufDemDisplay.json @@ -0,0 +1,48 @@ +{ + "id": 1602165579846, + "title": "Anzeige von Messwerten auf dem Display", + "steps": [ + { + "id": 1, + "type": "instruction", + "headline": "Einführung", + "text": "Das OLED Display ermöglicht dir Messwerte und vieles weitere anzuzeigen. Das Display bestitzt 128 Pixel in der X-Richtung und 64 Pixel in der Y-Richtung. Der Ursprung des Koordinatensystems liegt beim Display oben links in der Ecke. In diesem kurzen Tutorial zeigen wir dir, wie du Messwerte auf dem Display anzeigt. ", + "hardware": [ + "hdc1080", + "oled", + "senseboxmcu", + "jst-jst" + ], + "requirements": [ + 1602160534286 + ] + }, + { + "id": 2, + "type": "instruction", + "headline": "Anschluss des Displays", + "text": "Schließe das Display mit dem JST-JST Kabel an einen der 5 I2C Ports auf der senseBox MCU an. Verbinde auch den Sensor mit dem JST-JST Kabel mit einem weiteren I2C Port. " + }, + { + "id": 3, + "type": "instruction", + "headline": "Display initialsieren", + "text": "Im ersten Schritt muss das Display initialisiert werden. Verwende dazu den Block \"Display initialisieren\" in der Setup () Funktion. ", + "xml": "\n \n \n \n \n \n \n \n \n WHITE,BLACK\n 1\n 0\n 0\n \n \n Temperature\n \n \n \n \n \n \n \n" + }, + { + "id": 4, + "type": "instruction", + "headline": "Anzeige auf dem Display", + "text": "Verwende im nächsten Schritt den Block \"Zeige auf dem Display\" in der Endlosschleife, um die Anzeige auf dem Display zu starten. Zusätzlich muss mit dem Block \"Schreibe Text/Zahl\" noch angegeben werden wo auf dem Display und was auf dem Display anzeigt werden soll. Über die X und Y-Koordinaten kannst du festlegen wo auf dem Display dein Text/deine Zahl anzeigt werden soll. Beachte: In Schriftgröße 1 werden 8 Pixel in der Höhe für eine Zahl benötigt (in Schriftgröße 2 - 16). In das offene Feld \"Wert\" kannst du einfach deinen Block für den Temperatur- und Luftfeuchtigkeitssensor ziehen. Am Ende der Endlosschleife sollte das Display immer wieder gelöscht werden, um zu verhindern, dass Sachen übereinander geschrieben werden.", + "xml": "\n \n \n \n \n \n \n \n \n WHITE,BLACK\n 1\n 0\n 0\n \n \n Temperature\n \n \n \n \n \n \n \n \n \n \n" + }, + { + "id": 5, + "type": "task", + "headline": "Aufgabe 1: Anzeige der Temperatur auf dem Display", + "text": "Verwende die zuvor kennengelernten Blöcke, um die Messwerte des Temperatur- und Luftfeuchtigkeitssensor auf dem Display anzuzeigen. ", + "xml": "\n \n \n \n \n \n \n \n \n WHITE,BLACK\n 1\n 0\n 0\n \n \n Temperature\n \n \n \n \n \n \n \n \n \n \n" + } + ] +} \ No newline at end of file diff --git a/src/data/ErsteSchritte.json b/src/data/ErsteSchritte.json new file mode 100644 index 0000000..b844746 --- /dev/null +++ b/src/data/ErsteSchritte.json @@ -0,0 +1 @@ +{"id":1602160534286,"title":"Erste Schritte","steps":[{"id":1,"type":"instruction","headline":"Erste Schritte","text":"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-470ohm"],"requirements":[]},{"id":2,"type":"instruction","headline":"Aufbau der Schaltung","text":"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","text":"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":"\n \n"},{"id":4,"type":"instruction","headline":"Leuchten der LED","text":"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":"\n \n 1\n HIGH\n \n"},{"id":5,"type":"task","headline":"Aufgabe 1","text":"Verwende den zuvor kennengelernten Block, um die LED zum leuchten zu bringen.","xml":"\n \n \n \n 1\n HIGH\n \n \n \n"},{"id":6,"type":"instruction","headline":"Programmcode übertragen","text":"Super! Du hast dein erstes Programm erstellt und kompiliert. In nächsten Schritt muss das Programm auf deine senseBox MCU übertragen werden. Schließe diese mithilfe des USB Kabel an deinem Computer an und drücke 2 mal schnell hintereinander auf den roten Reset Button, um die senseBox MCU in den Lernmodus zu versetzten. Die senseBox MCU erscheint nun als USB-Gerät in deinem Dateiexplorer. Kopiere anschließend das Programm auf die senseBox MCU.","media":{"youtube":"jzlOJ7Zuqqw"}}]} \ No newline at end of file diff --git a/src/data/Wenn-DannBedingungen.json b/src/data/Wenn-DannBedingungen.json new file mode 100644 index 0000000..8ade20c --- /dev/null +++ b/src/data/Wenn-DannBedingungen.json @@ -0,0 +1,44 @@ +{ + "id": 1602160884767, + "title": "Wenn-Dann Bedingungen", + "steps": [ + { + "id": 1, + "type": "instruction", + "headline": "Wenn-Dann-Was?", + "text": "In diesem Tutorial lernst du die Verwendung von Wenn-Dann Bedingungen kennen. Die Wenn-Dann Bedingung ist eine der wichtigsten Kontrollstrukturen in der Programmierung und hilft dir dabei auf bestimmte Zustände einzugehen. ", + "hardware": [ + "senseboxmcu", + "breadboard", + "jst-adapter", + "resistor-470ohm" + ], + "requirements": [ + 1602160534286 + ] + }, + { + "id": 2, + "type": "instruction", + "headline": "Aufbau der Hardware", + "text": "Verbinde die LED mit Hilfe des JST-Adapter Kabel und dem 470 Ohm Widerstand mit einem der 3 digital/analog Ports der senseBox MCU. ", + "media": { + "picture": "01_circuit.png" + } + }, + { + "id": 3, + "type": "instruction", + "headline": "Die Wenn-Dann Bedingung", + "text": "Eine Wenn-Dann Bedingung kann dazu verwendet werden bestimmten Zeilen Code auszuführen, wenn eine bestimmte Bedingung erfüllt ist. \n\nwenn Bedingung dann\n Anweisung(en)\nende\n\nDas Beispiel unten zeigt dir wie eine Wenn-Dann Bedingung aufgebaut ist. Es kann zum Beispiel die Temperatur mit einem Wert verglichen werden. Ist dieser Vergleich wahr (true), dann werden die Blöcke ausgeführt, die neben \"dann\" stehen ausgeführt. Ist die Bedingung nicht erfüllt (false) dann wird einfach der Programmcode unterhalb der Wenn-Dann Bedingung ausgeführt.\n", + "xml": "\n \n \n \n \n \n EQ\n \n \n Temperature\n \n \n \n \n \n \n \n \n 0\n \n" + }, + { + "id": 4, + "type": "task", + "headline": "Leuchten der LED auf Knopfdruck", + "text": "Lasse die LED leuchten, wenn der \"Button\" auf der senseBox MCU gedrückt wird. Den Block für den Button findest du unter \"Sensoren\".", + "xml": "\n \n \n \n \n \n \n EQ\n \n \n isPressed\n 0\n \n \n \n \n TRUE\n \n \n \n \n \n \n 1\n HIGH\n \n \n \n \n EQ\n \n \n isPressed\n 0\n \n \n \n \n FALSE\n \n \n \n \n \n \n 1\n LOW\n \n \n \n \n \n" + } + ] +} \ No newline at end of file diff --git a/src/data/loops_01.json b/src/data/loops_01.json new file mode 100644 index 0000000..1f445e0 --- /dev/null +++ b/src/data/loops_01.json @@ -0,0 +1,50 @@ +{ + "id": 1602162097684, + "title": "Schleifen", + "steps": [ + { + "id": 1, + "type": "instruction", + "headline": "Schleifen", + "text": "In diesem Tutorial wird die Verwendung von Schleifen eingeführt. Eine Schleife (auch „Wiederholung“ oder englisch loop) ist eine Kontrollstruktur in Programmiersprachen. Sie wiederholt einen Anweisungs-Block – den sogenannten Schleifenrumpf oder Schleifenkörper –, solange die Schleifenbedingung als Laufbedingung gültig bleibt bzw. als Abbruchbedingung nicht eintritt. Schleifen, deren Schleifenbedingung immer zur Fortsetzung führt oder die keine Schleifenbedingung haben, sind Endlosschleifen. Die Endlosschleife hast du bereits im ersten Tutorial \"Erste Schritte\" kennengelernt", + "hardware": [ + "breadboard", + "jst-adapter", + "senseboxmcu", + "led", + "resistor-470ohm" + ], + "requirements": [ + 1602160534286 + ] + }, + { + "id": 2, + "type": "instruction", + "headline": "Verwendung von Schleifen", + "text": "Die Blöcke findest du in der Kategorie \"Schleifen\". Die einfachste Schleife, die du Verwenden kannst, ist der Block \"Wiederhole 10 mal\". Bei diesem Block kannst du die Blöcke, die eine bestimmte Zahl wiederholt werden soll einfach in den offenen Block abschnitt ziehen. ", + "xml": "\n \n \n \n 10\n \n \n \n" + }, + { + "id": 3, + "type": "task", + "headline": "Verwendung von Schleifen", + "text": "Lass die LED genau 5 mal in einem Abstand von 1000 Millisekunden blinken. Anschließend soll die LED ausgeschaltet werden. ", + "xml": "\n \n \n \n \n \n 5\n \n \n \n \n 1\n HIGH\n \n \n \n \n 1000\n \n \n \n \n 1\n LOW\n \n \n \n \n 1000\n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "id": 4, + "type": "instruction", + "headline": "Schleife mit Laufzeitvariable", + "text": "Mit diesem Block lässt sich die Schleife noch genauer Steuern und beeinflussen. Hierbei wird nicht nur angegeben wie oft die Anweisungen innerhalb der Schleife wiederholt werden sondern auch welche Variable zum zählen verwendet wird und in welchen Schritten gezählt werden soll. Der Vorteil bei diesem Block ist, dass die Wert der Variable auch innerhalb der Anweisungen verwendet werden kann. So kannst zum Beispiel die Variable \"i\" verwenden um die Blinkfrequenz zu beeinflussen.", + "xml": "\n \n i\n \n \n \n \n i\n \n \n 100\n \n \n \n \n 1000\n \n \n \n \n 100\n \n \n \n \n 1\n HIGH\n \n \n \n \n i\n \n \n \n \n 1\n LOW\n \n \n \n \n i\n \n \n \n \n \n \n \n \n \n \n \n \n \n" + }, + { + "id": 5, + "type": "task", + "headline": "Schleifen mit Laufzeitvariable", + "text": "Verwende die Schleife mit Laufzeitvariable und beeinflusse die Blinkfrequenz mithilfe dem Wert der Variable.", + "xml": "\n \n i\n \n \n \n \n i\n \n \n 100\n \n \n \n \n 1000\n \n \n \n \n 100\n \n \n \n \n 1\n HIGH\n \n \n \n \n i\n \n \n \n \n 1\n LOW\n \n \n \n \n i\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 1000\n \n" + } + ] +} \ No newline at end of file diff --git a/src/data/tutorials.js b/src/data/tutorials.js new file mode 100644 index 0000000..67471a9 --- /dev/null +++ b/src/data/tutorials.js @@ -0,0 +1,10 @@ + + +let tutorials = [ + require('./ErsteSchritte.json'), + require('./loops_01.json'), + require('./Wenn-DannBedingungen.json'), + require('./variablen_01.json'), + require('./AnzeigeVonMesswertenAufDemDisplay.json') +] +module.exports = tutorials; \ No newline at end of file diff --git a/src/data/tutorials.json b/src/data/tutorials.json deleted file mode 100644 index 67f0384..0000000 --- a/src/data/tutorials.json +++ /dev/null @@ -1,90 +0,0 @@ -[ - { - "id":1, - "title":"Erste Schritte", - "steps":[ - { - "id":1, - "type":"instruction", - "headline":"Erste Schritte", - "text":"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-470ohm"], - "requirements":[] - }, - { - "id":2, - "type":"instruction", - "headline":"Aufbau der Schaltung", - "text":"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", - "text":"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", - "text":"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", - "text":"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", - "text": "In diesem Tutorial lernst du wie man die senseBox mit dem Internet verbindest.", - "hardware": ["senseboxmcu", "wifi-bee"], - "requirements": [1] - }, - { - "id": 2, - "type": "instruction", - "headline": "Programmierung", - "text": "Man benötigt folgenden Block:", - "media": { - "picture": "block_en.svg" - }, - "xml": "SSIDPassword" - }, - { - "id": 3, - "type": "instruction", - "headline": "Block richtig einbinden", - "text": "Dies ist ein Test.", - "media": { - "youtube": "sf3RzXq6iVo" - } - }, - { - "id": 4, - "type": "task", - "headline": "Aufgabe 1", - "text": "Stelle eine WLAN-Verbindung mit einem beliebigen Netzwerk her.", - "xml": "SSIDPassword" - }, - { - "id": 5, - "type": "task", - "headline": "Aufgabe 2", - "text": "Versuche das gleiche einfach nochmal. Übung macht den Meister! ;)", - "xml": "SSIDPassword" - } - ] - } -] diff --git a/src/data/variablen_01.json b/src/data/variablen_01.json new file mode 100644 index 0000000..7f73f99 --- /dev/null +++ b/src/data/variablen_01.json @@ -0,0 +1,41 @@ +{ + "id": 1602162172424, + "title": "Verwendung von Variablen", + "steps": [ + { + "id": 1, + "type": "instruction", + "headline": "Variablen", + "text": "Variablen, auch Platzhalter genannt, werden in der Informatik für verschiedene Dinge genutzt. Sie sind eine Art Kiste, die mit einem Namen versehen ist. In dieser Kiste kannst du verschiedene Dinge hinterlegen (z.B. Zahlen und Texte) und diese später wieder abrufen. ", + "hardware": [ + "senseboxmcu", + "oled", + "jst-jst", + "hdc1080" + ], + "requirements": [ + 1602160534286 + ] + }, + { + "id": 2, + "type": "instruction", + "headline": "Aufbau der Hardware", + "text": "Verbinde das Display und den Temperatur- und Luftfeuchtigkeitssensor jeweils mit einem JST-JST Kabel mit einem der 5 I2C Ports auf der senseBox MCU." + }, + { + "id": 3, + "type": "instruction", + "headline": "Variablen in Blockly", + "text": "Variablen können ihren Wert im Laufe des Programmes verändern, sodass du zum Beispiel der Variable „Temperatur“ immer den aktuell gemessenen Temperaturwert zuweist. Eine Variablen besitzt immer einer bestimmten Datentyp. Der Datentyp gibt im Endeffekt an, wie groß die Kiste ist und wie der Wert aussehen kann, der in der Variable gespeichert werden kann. \n\nVariablen - Datentypen\nJe nachdem, was du in einer Variable speichern möchtest, musst du den richten Datentyp auswählen.\nZeichen: Für einzelne Textzeichen\nText: Für ganze Wörter oder Sätze\nZahl: Für Zahlen von -32768 bis +32768\nGroße Zahl: Für Zahlen von -2147483648 bis\n +2147483648\nDezimalzahl: Für Kommazahlen (z.B. 25,3)", + "xml": "\n \n Temperatur\n \n \n \n \n \n \n \n Temperatur\n \n \n Temperature\n \n \n \n \n \n \n WHITE,BLACK\n 1\n 0\n 0\n \n \n \n \n \n \n \n \n Temperatur\n \n" + }, + { + "id": 4, + "type": "task", + "headline": "Aufgabe 1:", + "text": "Erste 2 Variablen vom Typ \"Kommazahl\" und weise die Werte für Temperatur und Luftfeuchtigkeit zu. Lasse anschließend den Wert der Variablen auf dem Display anzeigen.", + "xml": "\n \n Temperatur\n Luftfeuchte\n \n \n \n \n \n \n \n Temperatur\n \n \n Temperature\n \n \n \n \n Luftfeuchte\n \n \n Temperature\n \n \n \n \n \n \n WHITE,BLACK\n 1\n 0\n 0\n \n \n Luftfeuchte\n \n \n \n \n WHITE,BLACK\n 1\n 0\n 20\n \n \n Temperatur\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" + } + ] +} \ No newline at end of file diff --git a/src/reducers/tutorialReducer.js b/src/reducers/tutorialReducer.js index e96a132..c0eb18b 100644 --- a/src/reducers/tutorialReducer.js +++ b/src/reducers/tutorialReducer.js @@ -1,41 +1,41 @@ import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_STEP } from '../actions/types'; -import tutorials from '../data/tutorials.json'; +import tutorials from '../data/tutorials'; const initialStatus = () => { - if(window.localStorage.getItem('status')){ + if (window.localStorage.getItem('status')) { var status = JSON.parse(window.localStorage.getItem('status')); var existingTutorialIds = tutorials.map((tutorial, i) => { var tutorialsId = tutorial.id; var statusIndex = status.findIndex(status => status.id === tutorialsId); - if(statusIndex > -1){ + if (statusIndex > -1) { var tasks = tutorial.steps.filter(step => step.type === 'task'); var existingTaskIds = tasks.map((task, j) => { var tasksId = task.id; - if(status[statusIndex].tasks.findIndex(task => task.id === tasksId) === -1){ + if (status[statusIndex].tasks.findIndex(task => task.id === tasksId) === -1) { // task does not exist - status[statusIndex].tasks.push({id: tasksId}); + status[statusIndex].tasks.push({ id: tasksId }); } return tasksId; }); // deleting old tasks which do not longer exist - if(existingTaskIds.length > 0){ + if (existingTaskIds.length > 0) { status[statusIndex].tasks = status[statusIndex].tasks.filter(task => existingTaskIds.indexOf(task.id) > -1); } } - else{ - status.push({id: tutorialsId, tasks: tutorial.steps.filter(step => step.type === 'task').map(task => {return {id: task.id};})}); + else { + status.push({ id: tutorialsId, tasks: tutorial.steps.filter(step => step.type === 'task').map(task => { return { id: task.id }; }) }); } return tutorialsId; }); // deleting old tutorials which do not longer exist - if(existingTutorialIds.length > 0){ + 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};})};}); + return tutorials.map(tutorial => { return { id: tutorial.id, tasks: tutorial.steps.filter(step => step.type === 'task').map(task => { return { id: task.id }; }) }; }); }; const initialState = { @@ -45,8 +45,8 @@ const initialState = { change: 0 }; -export default function(state = initialState, action){ - switch(action.type){ +export default function (state = initialState, action) { + switch (action.type) { case TUTORIAL_SUCCESS: case TUTORIAL_ERROR: case TUTORIAL_XML: