Merge pull request #12 from sensebox/new-tutorial-structure
New tutorial structure
This commit is contained in:
commit
e1f3bbdf03
Binary file not shown.
Before Width: | Height: | Size: 630 KiB After Width: | Height: | Size: 466 KiB |
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 () {
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
@ -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.)
|
@ -252,3 +252,47 @@ Blockly.Arduino.sensebox_button = function () {
|
||||
}
|
||||
return [code, Blockly.Arduino.ORDER_ATOMIC];
|
||||
};
|
||||
|
||||
/**
|
||||
* 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];
|
||||
|
||||
}
|
@ -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'],
|
||||
|
@ -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)";
|
||||
|
||||
|
@ -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)";
|
@ -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 {
|
||||
<Block type="sensebox_sensor_sds011" />
|
||||
<Block type="sensebox_sensor_pressure" />
|
||||
<Block type="sensebox_sensor_bme680_bsec" />
|
||||
<Block type="sensebox_scd30" />
|
||||
<Block type="sensebox_sensor_ultrasonic_ranger" />
|
||||
<Block type="sensebox_sensor_sound" />
|
||||
<Block type="sensebox_button" />
|
||||
|
@ -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 (
|
||||
<div style={{}}>
|
||||
{this.props.iconButton ?
|
||||
<Tooltip title='Blöcke kompilieren' arrow style={{marginRight: '5px'}}>
|
||||
<Tooltip title='Blöcke kompilieren' arrow style={{ marginRight: '5px' }}>
|
||||
<IconButton
|
||||
className={this.props.classes.button}
|
||||
onClick={() => this.compile()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCogs} size="xs"/>
|
||||
<FontAwesomeIcon icon={faCogs} size="xs" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
:
|
||||
:
|
||||
<Button style={{ float: 'right', color: 'white' }} variant="contained" color="primary" onClick={() => this.compile()}>
|
||||
<FontAwesomeIcon icon={faCogs} style={{marginRight: '5px'}}/> Kompilieren
|
||||
<FontAwesomeIcon icon={faCogs} style={{ marginRight: '5px' }} /> Kompilieren
|
||||
</Button>
|
||||
}
|
||||
<Backdrop className={this.props.classes.backdrop} open={this.state.progress}>
|
||||
@ -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 ?
|
||||
<div style={{marginTop: '10px'}}>
|
||||
<TextField autoFocus placeholder='Dateiname' value={this.state.name} onChange={this.setFileName} style={{marginRight: '10px'}}/>
|
||||
<div style={{ marginTop: '10px' }}>
|
||||
<TextField autoFocus placeholder='Dateiname' value={this.state.name} onChange={this.setFileName} style={{ marginRight: '10px' }} />
|
||||
<Button disabled={!this.state.name} variant='contained' color='primary' onClick={() => this.download()}>Eingabe</Button>
|
||||
</div>
|
||||
: null}
|
||||
: null}
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
@ -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));
|
||||
|
@ -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 (
|
||||
<div>
|
||||
<Breadcrumbs content={[{link: '/tutorial', title: 'Tutorial'}, {link: '/tutorial/builder', title: 'Builder'}]}/>
|
||||
<Breadcrumbs content={[{ link: '/tutorial', title: 'Tutorial' }, { link: '/tutorial/builder', title: 'Builder' }]} />
|
||||
|
||||
<h1>Tutorial-Builder</h1>
|
||||
|
||||
{/*upload JSON*/}
|
||||
<div ref={this.inputRef}>
|
||||
<input
|
||||
style={{display: 'none'}}
|
||||
style={{ display: 'none' }}
|
||||
accept="application/json"
|
||||
onChange={(e) => {this.uploadJsonFile(e.target.files[0])}}
|
||||
onChange={(e) => { this.uploadJsonFile(e.target.files[0]) }}
|
||||
id="open-json"
|
||||
type="file"
|
||||
/>
|
||||
<label htmlFor="open-json">
|
||||
<Button component="span" style={{marginRight: '10px', marginBottom: '10px'}} variant='contained' color='primary'>Datei laden</Button>
|
||||
<Button component="span" style={{ marginRight: '10px', marginBottom: '10px' }} variant='contained' color='primary'>Datei laden</Button>
|
||||
</label>
|
||||
<Button style={{marginRight: '10px', marginBottom: '10px'}} variant='contained' color='primary' onClick={() => this.uploadJsonString()}>String laden</Button>
|
||||
<Button style={{ marginRight: '10px', marginBottom: '10px' }} variant='contained' color='primary' onClick={() => this.uploadJsonString()}>String laden</Button>
|
||||
</div>
|
||||
<Divider variant='fullWidth' style={{margin: '10px 0 15px 0'}}/>
|
||||
<Divider variant='fullWidth' style={{ margin: '10px 0 15px 0' }} />
|
||||
|
||||
{/*Tutorial-Builder-Form*/}
|
||||
{this.props.error.type ?
|
||||
<FormHelperText style={{lineHeight: 'initial'}} className={this.props.classes.errorColor}>{`Ein Tutorial muss mindestens jeweils eine Instruktion und eine Aufgabe enthalten.`}</FormHelperText>
|
||||
: null}
|
||||
<Id error={this.props.error.id} value={this.props.id}/>
|
||||
<Textfield value={this.props.title} property={'title'} label={'Titel'} error={this.props.error.title}/>
|
||||
<FormHelperText style={{ lineHeight: 'initial' }} className={this.props.classes.errorColor}>{`Ein Tutorial muss mindestens jeweils eine Instruktion und eine Aufgabe enthalten.`}</FormHelperText>
|
||||
: null}
|
||||
{/* <Id error={this.props.error.id} value={this.props.id} /> */}
|
||||
<Textfield value={this.props.title} property={'title'} label={'Titel'} error={this.props.error.title} />
|
||||
|
||||
{this.props.steps.map((step, i) =>
|
||||
<Step step={step} index={i} key={i}/>
|
||||
<Step step={step} index={i} key={i} />
|
||||
)}
|
||||
|
||||
{/*submit or reset*/}
|
||||
<Divider variant='fullWidth' style={{margin: '30px 0 10px 0'}}/>
|
||||
<Button style={{marginRight: '10px', marginTop: '10px'}} variant='contained' color='primary' onClick={() => this.submit()}>Tutorial-Vorlage erstellen</Button>
|
||||
<Button style={{marginTop: '10px'}} variant='contained' onClick={() => this.reset()}>Zurücksetzen</Button>
|
||||
<Divider variant='fullWidth' style={{ margin: '30px 0 10px 0' }} />
|
||||
<Button style={{ marginRight: '10px', marginTop: '10px' }} variant='contained' color='primary' onClick={() => this.submit()}>Tutorial-Vorlage erstellen</Button>
|
||||
<Button style={{ marginTop: '10px' }} variant='contained' onClick={() => this.reset()}>Zurücksetzen</Button>
|
||||
|
||||
<Backdrop className={this.props.classes.backdrop} open={this.props.isProgress}>
|
||||
<CircularProgress color="inherit" />
|
||||
@ -182,16 +188,16 @@ class Builder extends Component {
|
||||
button={'Schließen'}
|
||||
actions={
|
||||
this.state.string ?
|
||||
<div>
|
||||
<Button disabled={this.props.error.json || this.props.json === ''} variant='contained' onClick={() => {this.toggle(); this.props.progress(true); this.readJson(this.props.json, false);}} color="primary">Bestätigen</Button>
|
||||
<Button onClick={() => {this.toggle(); this.props.jsonString('');}} color="primary">Abbrechen</Button>
|
||||
</div>
|
||||
: null
|
||||
<div>
|
||||
<Button disabled={this.props.error.json || this.props.json === ''} variant='contained' onClick={() => { this.toggle(); this.props.progress(true); this.readJson(this.props.json, false); }} color="primary">Bestätigen</Button>
|
||||
<Button onClick={() => { this.toggle(); this.props.jsonString(''); }} color="primary">Abbrechen</Button>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
>
|
||||
{this.state.string ?
|
||||
<Textfield value={this.props.json} property={'json'} label={'JSON'} multiline error={this.props.error.json}/>
|
||||
: null}
|
||||
<Textfield value={this.props.json} property={'json'} label={'JSON'} multiline error={this.props.error.json} />
|
||||
: null}
|
||||
</Dialog>
|
||||
|
||||
<Snackbar
|
||||
@ -230,4 +236,4 @@ const mapStateToProps = state => ({
|
||||
isProgress: state.builder.progress
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { checkError, readJSON, jsonString, progress, resetTutorial })(withStyles(styles, {withTheme: true})(Builder));
|
||||
export default connect(mapStateToProps, { checkError, readJSON, jsonString, progress, resetTutorial })(withStyles(styles, { withTheme: true })(Builder));
|
||||
|
@ -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 (
|
||||
<div style={{marginBottom: '10px', padding: '18.5px 14px', borderRadius: '25px', border: '1px solid lightgrey', width: 'calc(100% - 28px)'}}>
|
||||
<FormLabel style={{color: 'black'}}>Hardware</FormLabel>
|
||||
<FormHelperText style={this.props.error ? {lineHeight: 'initial', marginTop: '5px'} : {marginTop: '5px', lineHeight: 'initial', marginBottom: '10px'}}>Beachte, dass die Reihenfolge des Auswählens maßgebend ist.</FormHelperText>
|
||||
<div style={{ marginBottom: '10px', padding: '18.5px 14px', borderRadius: '25px', border: '1px solid lightgrey', width: 'calc(100% - 28px)' }}>
|
||||
<FormLabel style={{ color: 'black' }}>Hardware</FormLabel>
|
||||
<FormHelperText style={this.props.error ? { lineHeight: 'initial', marginTop: '5px' } : { marginTop: '5px', lineHeight: 'initial', marginBottom: '10px' }}>Beachte, dass die Reihenfolge des Auswählens maßgebend ist.</FormHelperText>
|
||||
{this.props.error ? <FormHelperText className={this.props.classes.errorColor}>Wähle mindestens eine Hardware-Komponente aus.</FormHelperText> : null}
|
||||
<GridList cellHeight={100} cols={cols} spacing={10}>
|
||||
{hardware.map((picture,i) => (
|
||||
<GridListTile key={i} onClick={() => this.onChange(picture.id)} classes={{tile: this.props.value.filter(value => value === picture.id).length > 0 ? this.props.classes.active : this.props.classes.border}}>
|
||||
<div style={{margin: 'auto', width: 'max-content'}}>
|
||||
<img src={`/media/hardware/${picture.src}`} alt={picture.name} height={100} />
|
||||
</div>
|
||||
<GridListTileBar
|
||||
classes={{root: this.props.classes.multiGridListTile}}
|
||||
title={
|
||||
<div style={{overflow: 'hidden', textOverflow: 'ellipsis'}} className={this.props.classes.multiGridListTileTitle}>
|
||||
{picture.name}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</GridListTile>
|
||||
))}
|
||||
{hardware.map((picture, i) => (
|
||||
<GridListTile key={i} onClick={() => this.onChange(picture.id)} classes={{ tile: this.props.value.filter(value => value === picture.id).length > 0 ? this.props.classes.active : this.props.classes.border }}>
|
||||
<div style={{ margin: 'auto', width: 'max-content' }}>
|
||||
<img src={`/media/hardware/${picture.src}`} alt={picture.name} height={100} />
|
||||
</div>
|
||||
<GridListTileBar
|
||||
classes={{ root: this.props.classes.multiGridListTile }}
|
||||
title={
|
||||
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }} className={this.props.classes.multiGridListTileTitle}>
|
||||
{picture.name}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</GridListTile>
|
||||
))}
|
||||
</GridList>
|
||||
</div>
|
||||
);
|
||||
|
@ -28,81 +28,83 @@ const styles = theme => ({
|
||||
class Id extends Component {
|
||||
|
||||
handleChange = (e) => {
|
||||
var value = parseInt(e.target.value);
|
||||
if(Number.isInteger(value) && value > 0){
|
||||
var value =
|
||||
|
||||
parseInt(e.target.value);
|
||||
if (Number.isInteger(value) && value > 0) {
|
||||
this.props.tutorialId(value);
|
||||
if(this.props.error){
|
||||
if (this.props.error) {
|
||||
this.props.deleteError(undefined, 'id');
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.props.tutorialId(value.toString());
|
||||
this.props.setError(undefined,'id');
|
||||
this.props.setError(undefined, 'id');
|
||||
}
|
||||
};
|
||||
|
||||
handleCounter = (step) => {
|
||||
if(this.props.value+step < 1){
|
||||
this.props.setError(undefined,'id');
|
||||
if (this.props.value + step < 1) {
|
||||
this.props.setError(undefined, 'id');
|
||||
}
|
||||
else if(this.props.error){
|
||||
else if (this.props.error) {
|
||||
this.props.deleteError(undefined, 'id');
|
||||
}
|
||||
if(!this.props.value || !Number.isInteger(this.props.value)){
|
||||
this.props.tutorialId(0+step);
|
||||
if (!this.props.value || !Number.isInteger(this.props.value)) {
|
||||
this.props.tutorialId(0 + step);
|
||||
}
|
||||
else {
|
||||
this.props.tutorialId(this.props.value+step);
|
||||
this.props.tutorialId(this.props.value + step);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{display: 'inline-flex', marginTop: '15px'}}>
|
||||
<FormControl variant="outlined" style={{marginBottom: '10px', width: '250px'}}>
|
||||
<InputLabel
|
||||
htmlFor="id"
|
||||
classes={{shrink: this.props.error ? this.props.classes.errorColorShrink : null}}
|
||||
>
|
||||
ID
|
||||
<div style={{ display: 'inline-flex', marginTop: '15px' }}>
|
||||
<FormControl variant="outlined" style={{ marginBottom: '10px', width: '250px' }}>
|
||||
<InputLabel
|
||||
htmlFor="id"
|
||||
classes={{ shrink: this.props.error ? this.props.classes.errorColorShrink : null }}
|
||||
>
|
||||
ID
|
||||
</InputLabel>
|
||||
<OutlinedInput
|
||||
style={{borderRadius: '25px', padding: '0 0 0 10px', width: '200px'}}
|
||||
classes={{notchedOutline: this.props.error ? this.props.classes.errorBorder : null}}
|
||||
error={this.props.error}
|
||||
value={this.props.value}
|
||||
name='id'
|
||||
label='ID'
|
||||
id='id'
|
||||
onChange={this.handleChange}
|
||||
inputProps={{
|
||||
style: {marginRight: '10px'}
|
||||
}}
|
||||
endAdornment={
|
||||
<div style={{display: 'flex'}}>
|
||||
<Button
|
||||
disabled={this.props.value === 1 || !Number.isInteger(this.props.value)}
|
||||
onClick={() => this.handleCounter(-1)}
|
||||
variant='contained'
|
||||
color='primary'
|
||||
style={{borderRadius: '25px 0 0 25px', height: '56px', boxShadow: '0 0 transparent'}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faMinus} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => this.handleCounter(1)}
|
||||
variant='contained'
|
||||
color='primary'
|
||||
style={{borderRadius: '0 25px 25px 0', height: '56px', boxShadow: '0 0 transparent'}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
{this.props.error ? <FormHelperText className={this.props.classes.errorColor}>Gib eine positive ganzzahlige Zahl ein.</FormHelperText> : null}
|
||||
</FormControl>
|
||||
<FormHelperText style={{marginLeft: '-40px', marginTop: '5px', lineHeight: 'initial', marginBottom: '10px', width: '200px'}}>Beachte, dass die ID eindeutig sein muss. Sie muss sich also zu den anderen Tutorials unterscheiden.</FormHelperText>
|
||||
<OutlinedInput
|
||||
style={{ borderRadius: '25px', padding: '0 0 0 10px', width: '200px' }}
|
||||
classes={{ notchedOutline: this.props.error ? this.props.classes.errorBorder : null }}
|
||||
error={this.props.error}
|
||||
value={this.props.value}
|
||||
name='id'
|
||||
label='ID'
|
||||
id='id'
|
||||
onChange={this.handleChange}
|
||||
inputProps={{
|
||||
style: { marginRight: '10px' }
|
||||
}}
|
||||
endAdornment={
|
||||
<div style={{ display: 'flex' }}>
|
||||
<Button
|
||||
disabled={this.props.value === 1 || !Number.isInteger(this.props.value)}
|
||||
onClick={() => this.handleCounter(-1)}
|
||||
variant='contained'
|
||||
color='primary'
|
||||
style={{ borderRadius: '25px 0 0 25px', height: '56px', boxShadow: '0 0 transparent' }}
|
||||
>
|
||||
<FontAwesomeIcon icon={faMinus} />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => this.handleCounter(1)}
|
||||
variant='contained'
|
||||
color='primary'
|
||||
style={{ borderRadius: '0 25px 25px 0', height: '56px', boxShadow: '0 0 transparent' }}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
{this.props.error ? <FormHelperText className={this.props.classes.errorColor}>Gib eine positive ganzzahlige Zahl ein.</FormHelperText> : null}
|
||||
</FormControl>
|
||||
<FormHelperText style={{ marginLeft: '-40px', marginTop: '5px', lineHeight: 'initial', marginBottom: '10px', width: '200px' }}>Beachte, dass die ID eindeutig sein muss. Sie muss sich also zu den anderen Tutorials unterscheiden.</FormHelperText>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { changeContent } from '../../../actions/tutorialBuilderActions';
|
||||
|
||||
import tutorials from '../../../data/tutorials.json';
|
||||
import tutorials from '../../../data/tutorials';
|
||||
|
||||
import FormGroup from '@material-ui/core/FormGroup';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
@ -17,7 +17,7 @@ class Requirements extends Component {
|
||||
onChange = (e) => {
|
||||
var requirements = this.props.value;
|
||||
var value = parseInt(e.target.value)
|
||||
if(e.target.checked){
|
||||
if (e.target.checked) {
|
||||
requirements.push(value);
|
||||
}
|
||||
else {
|
||||
@ -28,9 +28,9 @@ class Requirements extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<FormControl style={{marginBottom: '10px', padding: '18.5px 14px', borderRadius: '25px', border: '1px solid lightgrey', width: 'calc(100% - 28px)'}}>
|
||||
<FormLabel style={{color: 'black'}}>Voraussetzungen</FormLabel>
|
||||
<FormHelperText style={{marginTop: '5px'}}>Beachte, dass die Reihenfolge des Anhakens maßgebend ist.</FormHelperText>
|
||||
<FormControl style={{ marginBottom: '10px', padding: '18.5px 14px', borderRadius: '25px', border: '1px solid lightgrey', width: 'calc(100% - 28px)' }}>
|
||||
<FormLabel style={{ color: 'black' }}>Voraussetzungen</FormLabel>
|
||||
<FormHelperText style={{ marginTop: '5px' }}>Beachte, dass die Reihenfolge des Anhakens maßgebend ist.</FormHelperText>
|
||||
<FormGroup>
|
||||
{tutorials.map((tutorial, i) =>
|
||||
<FormControlLabel
|
||||
|
@ -4,7 +4,6 @@ import Dialog from '../Dialog';
|
||||
|
||||
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 Link from '@material-ui/core/Link';
|
||||
@ -28,7 +27,8 @@ const styles = theme => ({
|
||||
color: theme.palette.text.primary
|
||||
},
|
||||
multiGridListTile: {
|
||||
background: fade(theme.palette.secondary.main, 0.5),
|
||||
background: "#4EAF47",
|
||||
opacity: 0.9,
|
||||
height: '30px'
|
||||
},
|
||||
multiGridListTileTitle: {
|
||||
@ -45,46 +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 (
|
||||
<div style={{marginTop: '10px', marginBottom: '5px'}}>
|
||||
<div style={{ marginTop: '10px', marginBottom: '5px' }}>
|
||||
<Typography>Für die Umsetzung benötigst du folgende Hardware:</Typography>
|
||||
|
||||
<GridList cellHeight={100} cols={cols} spacing={10}>
|
||||
{this.props.picture.map((picture,i) => {
|
||||
var hardwareInfo = hardware.filter(hardware => hardware.id === picture)[0];
|
||||
return(
|
||||
<GridListTile key={i}>
|
||||
<div style={{margin: 'auto', width: 'max-content'}}>
|
||||
<img src={`/media/hardware/${hardwareInfo.src}`} alt={hardwareInfo.name} height={100} style={{cursor: 'pointer'}} onClick={() => this.handleClickOpen(hardwareInfo)}/>
|
||||
</div>
|
||||
<GridListTileBar
|
||||
classes={{root: this.props.classes.multiGridListTile}}
|
||||
title={
|
||||
<div style={{overflow: 'hidden', textOverflow: 'ellipsis'}} className={this.props.classes.multiGridListTileTitle}>
|
||||
{hardwareInfo.name}
|
||||
</div>
|
||||
}
|
||||
actionIcon={
|
||||
<IconButton className={this.props.classes.expand} aria-label='Vollbild' onClick={() => this.handleClickOpen(hardwareInfo)}>
|
||||
<FontAwesomeIcon icon={faExpandAlt} size="xs"/>
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
</GridListTile>
|
||||
)})}
|
||||
{this.props.picture.map((picture, i) => {
|
||||
var hardwareInfo = hardware.filter(hardware => hardware.id === picture)[0];
|
||||
return (
|
||||
<GridListTile key={i}>
|
||||
<div style={{ margin: 'auto', width: 'max-content' }}>
|
||||
<img src={`/media/hardware/${hardwareInfo.src}`} alt={hardwareInfo.name} height={100} style={{ cursor: 'pointer' }} onClick={() => this.handleClickOpen(hardwareInfo)} />
|
||||
</div>
|
||||
<GridListTileBar
|
||||
classes={{ root: this.props.classes.multiGridListTile }}
|
||||
title={
|
||||
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }} className={this.props.classes.multiGridListTileTitle}>
|
||||
{hardwareInfo.name}
|
||||
</div>
|
||||
}
|
||||
actionIcon={
|
||||
<IconButton className={this.props.classes.expand} aria-label='Vollbild' onClick={() => this.handleClickOpen(hardwareInfo)}>
|
||||
<FontAwesomeIcon icon={faExpandAlt} size="xs" />
|
||||
</IconButton>
|
||||
}
|
||||
/>
|
||||
</GridListTile>
|
||||
)
|
||||
})}
|
||||
</GridList>
|
||||
|
||||
<Dialog
|
||||
style={{zIndex: 1500}}
|
||||
style={{ zIndex: 1500 }}
|
||||
open={this.state.open}
|
||||
title={`Hardware: ${this.state.hardwareInfo.name}`}
|
||||
content={this.state.content}
|
||||
@ -93,7 +94,7 @@ class Hardware extends Component {
|
||||
button={'Schließen'}
|
||||
>
|
||||
<div>
|
||||
<img src={`/media/hardware/${this.state.hardwareInfo.src}`} width="100%" alt={this.state.hardwareInfo.name}/>
|
||||
<img src={`/media/hardware/${this.state.hardwareInfo.src}`} width="100%" alt={this.state.hardwareInfo.name} />
|
||||
Weitere Informationen zur Hardware-Komponente findest du <Link href={this.state.hardwareInfo.url} color="primary">hier</Link>.
|
||||
</div>
|
||||
</Dialog>
|
||||
|
@ -5,7 +5,7 @@ import { connect } from 'react-redux';
|
||||
import clsx from 'clsx';
|
||||
import { withRouter, Link } from 'react-router-dom';
|
||||
|
||||
import tutorials from '../../data/tutorials.json';
|
||||
import tutorials from '../../data/tutorials';
|
||||
|
||||
import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
@ -63,46 +63,47 @@ class Requirement extends Component {
|
||||
render() {
|
||||
var tutorialIds = this.props.tutorialIds;
|
||||
return (
|
||||
<div style={{marginTop: '20px', marginBottom: '5px'}}>
|
||||
<Typography>Es bietet sich an folgende Tutorials vorab erfolgreich gelöst zu haben:</Typography>
|
||||
<div style={{ marginTop: '20px', marginBottom: '5px' }}>
|
||||
<Typography>Bevor du mit diesem Tutorial fortfährst solltest du folgende Tutorials erfolgreich abgeschlossen haben:</Typography>
|
||||
<List component="div">
|
||||
{tutorialIds.map((tutorialId, i) => {
|
||||
var title = tutorials.filter(tutorial => tutorial.id === tutorialId)[0].title;
|
||||
var status = this.props.status.filter(status => status.id === tutorialId)[0];
|
||||
var tasks = status.tasks;
|
||||
var error = status.tasks.filter(task => task.type === 'error').length > 0;
|
||||
var success = status.tasks.filter(task => task.type === 'success').length/tasks.length
|
||||
var success = status.tasks.filter(task => task.type === 'success').length / tasks.length
|
||||
var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other';
|
||||
return(
|
||||
return (
|
||||
<Link to={`/tutorial/${tutorialId}`} className={this.props.classes.link}>
|
||||
<Tooltip style={{height: '50px', width: '50px', position: 'absolute', background: 'white', zIndex: 1, borderRadius: '25px'}} title={error ? `Mind. eine Aufgabe im Tutorial wurde nicht richtig gelöst.` : success === 1 ? `Das Tutorial wurde bereits erfolgreich abgeschlossen.` : `Das Tutorial ist zu ${success * 100}% abgeschlossen.`} arrow>
|
||||
<Tooltip style={{ height: '50px', width: '50px', position: 'absolute', background: 'white', zIndex: 1, borderRadius: '25px' }} title={error ? `Mind. eine Aufgabe im Tutorial wurde nicht richtig gelöst.` : success === 1 ? `Das Tutorial wurde bereits erfolgreich abgeschlossen.` : `Das Tutorial ist zu ${success * 100}% abgeschlossen.`} arrow>
|
||||
<div>
|
||||
<div className={clsx(this.props.classes.outerDiv)} style={{width: '50px', height: '50px', border: 0}}>
|
||||
<svg style={{width: '100%', height: '100%'}}>
|
||||
<div className={clsx(this.props.classes.outerDiv)} style={{ width: '50px', height: '50px', border: 0 }}>
|
||||
<svg style={{ width: '100%', height: '100%' }}>
|
||||
{error || success === 1 ?
|
||||
<circle className={error ? this.props.classes.outerDivError : this.props.classes.outerDivSuccess} r="22.5" cx="50%" cy="50%" fill="none" stroke-width="5"></circle>
|
||||
: <circle className={this.props.classes.outerDivOther} r="22.5" cx="50%" cy="50%" fill="none" stroke-width="5"></circle>}
|
||||
: <circle className={this.props.classes.outerDivOther} r="22.5" cx="50%" cy="50%" fill="none" stroke-width="5"></circle>}
|
||||
{success < 1 && !error ?
|
||||
<circle className={this.props.classes.outerDivSuccess} style={{transform: 'rotate(-90deg)', transformOrigin: "50% 50%"}} r="22.5" cx="50%" cy="50%" fill="none" stroke-width="5" stroke-dashoffset={`${(22.5*2*Math.PI)*(1-success)}`} stroke-dasharray={`${(22.5*2*Math.PI)}`}>
|
||||
<circle className={this.props.classes.outerDivSuccess} style={{ transform: 'rotate(-90deg)', transformOrigin: "50% 50%" }} r="22.5" cx="50%" cy="50%" fill="none" stroke-width="5" stroke-dashoffset={`${(22.5 * 2 * Math.PI) * (1 - success)}`} stroke-dasharray={`${(22.5 * 2 * Math.PI)}`}>
|
||||
</circle>
|
||||
: null}
|
||||
: null}
|
||||
</svg>
|
||||
</div>
|
||||
<div className={clsx(this.props.classes.outerDiv, tutorialStatus === 'Error' ? this.props.classes.outerDivError : tutorialStatus === 'Success' ? this.props.classes.outerDivSuccess : null)}>
|
||||
<div className={this.props.classes.innerDiv}>
|
||||
{error || success === 1 ?
|
||||
<FontAwesomeIcon icon={tutorialStatus === 'Success' ? faCheck : faTimes}/>
|
||||
: <Typography variant='h7' className={success > 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success*100)}%</Typography>
|
||||
<FontAwesomeIcon icon={tutorialStatus === 'Success' ? faCheck : faTimes} />
|
||||
: <Typography variant='h7' className={success > 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success * 100)}%</Typography>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div style={{height: '50px', width: 'calc(100% - 25px)', transform: 'translate(25px)'}} className={this.props.classes.hoverLink}>
|
||||
<Typography style={{margin: 0, position: 'absolute', top: '50%', transform: 'translate(45px, -50%)', maxHeight: '50px', overflow: 'hidden', maxWidth: 'calc(100% - 45px)'/*, textOverflow: 'ellipsis', whiteSpace: 'pre-line', overflowWrap: 'anywhere'*/}}>{title}</Typography>
|
||||
<div style={{ height: '50px', width: 'calc(100% - 25px)', transform: 'translate(25px)' }} className={this.props.classes.hoverLink}>
|
||||
<Typography style={{ margin: 0, position: 'absolute', top: '50%', transform: 'translate(45px, -50%)', maxHeight: '50px', overflow: 'hidden', maxWidth: 'calc(100% - 45px)'/*, textOverflow: 'ellipsis', whiteSpace: 'pre-line', overflowWrap: 'anywhere'*/ }}>{title}</Typography>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
)
|
||||
}
|
||||
)}
|
||||
</List>
|
||||
</div>
|
||||
@ -120,4 +121,4 @@ const mapStateToProps = state => ({
|
||||
status: state.tutorial.status
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(withStyles(styles, {withTheme: true})(withRouter(Requirement)));
|
||||
export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(withRouter(Requirement)));
|
||||
|
@ -8,7 +8,7 @@ import { withRouter } from 'react-router-dom';
|
||||
import Compile from '../Compile';
|
||||
import Dialog from '../Dialog';
|
||||
|
||||
import tutorials from '../../data/tutorials.json';
|
||||
import tutorials from '../../data/tutorials';
|
||||
import { checkXml } from '../../helpers/compareXml';
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
@ -32,16 +32,16 @@ const styles = (theme) => ({
|
||||
|
||||
class SolutionCheck extends Component {
|
||||
|
||||
state={
|
||||
state = {
|
||||
open: false,
|
||||
msg: ''
|
||||
}
|
||||
|
||||
toggleDialog = () => {
|
||||
if(this.state.open){
|
||||
if (this.state.open) {
|
||||
this.setState({ open: false, msg: '' });
|
||||
}
|
||||
else{
|
||||
else {
|
||||
this.setState({ open: !this.state });
|
||||
}
|
||||
}
|
||||
@ -61,15 +61,15 @@ class SolutionCheck extends Component {
|
||||
<Tooltip title='Lösung kontrollieren' arrow>
|
||||
<IconButton
|
||||
className={this.props.classes.compile}
|
||||
style={{width: '40px', height: '40px', marginRight: '5px'}}
|
||||
style={{ width: '40px', height: '40px', marginRight: '5px' }}
|
||||
onClick={() => this.check()}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlay} size="xs"/>
|
||||
<FontAwesomeIcon icon={faPlay} size="xs" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Dialog
|
||||
style={{zIndex: 9999999}}
|
||||
style={{ zIndex: 9999999 }}
|
||||
fullWidth
|
||||
maxWidth={'sm'}
|
||||
open={this.state.open}
|
||||
@ -80,29 +80,29 @@ class SolutionCheck extends Component {
|
||||
button={'Schließen'}
|
||||
>
|
||||
{this.state.msg.type === 'success' ?
|
||||
<div style={{marginTop: '20px', display: 'flex'}}>
|
||||
<div style={{ marginTop: '20px', display: 'flex' }}>
|
||||
<Compile />
|
||||
{this.props.activeStep === steps.length-1 ?
|
||||
{this.props.activeStep === steps.length - 1 ?
|
||||
<Button
|
||||
style={{marginLeft: '10px'}}
|
||||
style={{ marginLeft: '10px' }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {this.toggleDialog(); this.props.history.push(`/tutorial/`)}}
|
||||
onClick={() => { this.toggleDialog(); this.props.history.push(`/tutorial/`) }}
|
||||
>
|
||||
Tutorials-Übersicht
|
||||
</Button>
|
||||
:
|
||||
:
|
||||
<Button
|
||||
style={{marginLeft: '10px'}}
|
||||
style={{ marginLeft: '10px' }}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {this.toggleDialog(); this.props.tutorialStep(this.props.activeStep + 1)}}
|
||||
onClick={() => { this.toggleDialog(); this.props.tutorialStep(this.props.activeStep + 1) }}
|
||||
>
|
||||
nächster Schritt
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
: null}
|
||||
: null}
|
||||
</Dialog>
|
||||
|
||||
</div>
|
||||
@ -125,4 +125,4 @@ const mapStateToProps = state => ({
|
||||
xml: state.workspace.code.xml
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { tutorialCheck, tutorialStep })(withStyles(styles, {withTheme: true})(withRouter(SolutionCheck)));
|
||||
export default connect(mapStateToProps, { tutorialCheck, tutorialStep })(withStyles(styles, { withTheme: true })(withRouter(SolutionCheck)));
|
||||
|
@ -6,7 +6,7 @@ import { withRouter } from 'react-router-dom';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
import tutorials from '../../data/tutorials.json';
|
||||
import tutorials from '../../data/tutorials';
|
||||
|
||||
import { fade } from '@material-ui/core/styles/colorManipulator';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
@ -58,31 +58,31 @@ class StepperHorizontal extends Component {
|
||||
var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other';
|
||||
var title = tutorials.filter(tutorial => tutorial.id === tutorialId)[0].title;
|
||||
return (
|
||||
<div style={{position: 'relative'}}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
{error || success > 0 ?
|
||||
<div style={{zIndex: -1, width: error ? 'calc(100% - 40px)' : `calc(${success*100}% - 40px)`, borderRadius: success === 1 || error ? '25px' : '25px 0 0 25px', position: 'absolute', margin: 0, left: 0}} className={clsx(this.props.classes.stepper, error ? this.props.classes.stepperError : this.props.classes.stepperSuccess)}>
|
||||
<div style={{ zIndex: -1, width: error ? 'calc(100% - 40px)' : `calc(${success * 100}% - 40px)`, borderRadius: success === 1 || error ? '25px' : '25px 0 0 25px', position: 'absolute', margin: 0, left: 0 }} className={clsx(this.props.classes.stepper, error ? this.props.classes.stepperError : this.props.classes.stepperSuccess)}>
|
||||
</div>
|
||||
: null}
|
||||
: null}
|
||||
{success < 1 && !error ?
|
||||
<div style={{zIndex: -2, width: `calc(${(1-success)*100}% - 40px)`, borderRadius: success === 0 ? '25px' : '0px 25px 25px 0', position: 'absolute', margin: 0, right: 0}} className={clsx(this.props.classes.stepper, this.props.classes.stepperOther)}>
|
||||
<div style={{ zIndex: -2, width: `calc(${(1 - success) * 100}% - 40px)`, borderRadius: success === 0 ? '25px' : '0px 25px 25px 0', position: 'absolute', margin: 0, right: 0 }} className={clsx(this.props.classes.stepper, this.props.classes.stepperOther)}>
|
||||
</div>
|
||||
: null}
|
||||
: null}
|
||||
<div className={this.props.classes.stepper}>
|
||||
<Button
|
||||
disabled={tutorialId === 1}
|
||||
onClick={() => {this.props.history.push(`/tutorial/${tutorialId-1}`)}}
|
||||
onClick={() => { this.props.history.push(`/tutorial/${tutorialId - 1}`) }}
|
||||
>
|
||||
{'<'}
|
||||
</Button>
|
||||
<Tooltip style={{display: 'flex', width: 'calc(100% - 64px - 64px)', justifyContent: 'center'}} title={title} arrow>
|
||||
<Tooltip style={{ display: 'flex', width: 'calc(100% - 64px - 64px)', justifyContent: 'center' }} title={title} arrow>
|
||||
<div>
|
||||
{tutorialStatus !== 'Other' ? <div className={tutorialStatus === 'Success' && success === 1 ? this.props.classes.iconDivSuccess : this.props.classes.iconDivError} style={{margin: 'auto 10px auto 0'}}><FontAwesomeIcon className={this.props.classes.icon} icon={tutorialStatus === 'Success' ? faCheck : faTimes}/></div> : null}
|
||||
<Typography variant='body2' style={{fontWeight: 'bold', fontSize: '1.75em', margin: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'rgba(0, 0, 0, 0.54)'}}>{title}</Typography>
|
||||
{tutorialStatus !== 'Other' ? <div className={tutorialStatus === 'Success' && success === 1 ? this.props.classes.iconDivSuccess : this.props.classes.iconDivError} style={{ margin: 'auto 10px auto 0' }}><FontAwesomeIcon className={this.props.classes.icon} icon={tutorialStatus === 'Success' ? faCheck : faTimes} /></div> : null}
|
||||
<Typography variant='body2' style={{ fontWeight: 'bold', fontSize: '1.75em', margin: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'rgba(0, 0, 0, 0.54)' }}>{title}</Typography>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Button
|
||||
disabled={tutorialId+1 > tutorials.length}
|
||||
onClick={() => {this.props.history.push(`/tutorial/${tutorialId+1}`)}}
|
||||
disabled={tutorialId + 1 > tutorials.length}
|
||||
onClick={() => { this.props.history.push(`/tutorial/${tutorialId + 1}`) }}
|
||||
>
|
||||
{'>'}
|
||||
</Button>
|
||||
@ -104,4 +104,4 @@ const mapStateToProps = state => ({
|
||||
currentTutorialId: state.tutorial.currentId
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(withRouter(withStyles(styles, {withTheme: true})(StepperHorizontal)));
|
||||
export default connect(mapStateToProps, null)(withRouter(withStyles(styles, { withTheme: true })(StepperHorizontal)));
|
||||
|
@ -13,24 +13,24 @@ import NotFound from '../NotFound';
|
||||
|
||||
import { detectWhitespacesAndReturnReadableResult } from '../../helpers/whitespace';
|
||||
|
||||
import tutorials from '../../data/tutorials.json';
|
||||
import tutorials from '../../data/tutorials';
|
||||
|
||||
import Card from '@material-ui/core/Card';
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
||||
class Tutorial extends Component {
|
||||
|
||||
componentDidMount(){
|
||||
componentDidMount() {
|
||||
this.props.tutorialId(Number(this.props.match.params.tutorialId));
|
||||
}
|
||||
|
||||
componentDidUpdate(props, state){
|
||||
if(props.currentTutorialId !== Number(this.props.match.params.tutorialId)){
|
||||
componentDidUpdate(props, state) {
|
||||
if (props.currentTutorialId !== Number(this.props.match.params.tutorialId)) {
|
||||
this.props.tutorialId(Number(this.props.match.params.tutorialId));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
componentWillUnmount() {
|
||||
this.props.tutorialId(null);
|
||||
this.props.workspaceName(null);
|
||||
}
|
||||
@ -43,30 +43,30 @@ class Tutorial extends Component {
|
||||
var name = step ? `${detectWhitespacesAndReturnReadableResult(tutorial.title)}_${detectWhitespacesAndReturnReadableResult(step.headline)}` : null;
|
||||
return (
|
||||
!Number.isInteger(currentTutorialId) || currentTutorialId < 1 || !tutorial ?
|
||||
<NotFound button={{title: 'Zurück zur Tutorials-Übersicht', link: '/tutorial'}}/>
|
||||
:
|
||||
<div>
|
||||
<Breadcrumbs content={[{link: '/tutorial', title: 'Tutorial'}, {link: `/tutorial/${currentTutorialId}`, title: tutorial.title}]}/>
|
||||
<NotFound button={{ title: 'Zurück zur Tutorials-Übersicht', link: '/tutorial' }} />
|
||||
:
|
||||
<div>
|
||||
<Breadcrumbs content={[{ link: '/tutorial', title: 'Tutorial' }, { link: `/tutorial/${currentTutorialId}`, title: tutorial.title }]} />
|
||||
|
||||
<StepperHorizontal />
|
||||
<StepperHorizontal />
|
||||
|
||||
<div style={{display: 'flex'}}>
|
||||
<StepperVertical steps={steps}/>
|
||||
{/* calc(Card-padding: 10px + Button-height: 35px + Button-marginTop: 15px)*/}
|
||||
<Card style={{padding: '10px 10px 60px 10px', display: 'block', position: 'relative', height: 'max-content', width: '100%'}}>
|
||||
{step ?
|
||||
step.type === 'instruction' ?
|
||||
<Instruction step={step}/>
|
||||
: <Assessment step={step} name={name}/> // if step.type === 'assessment'
|
||||
: null}
|
||||
<div style={{ display: 'flex' }}>
|
||||
<StepperVertical steps={steps} />
|
||||
{/* calc(Card-padding: 10px + Button-height: 35px + Button-marginTop: 15px)*/}
|
||||
<Card style={{ padding: '10px 10px 60px 10px', display: 'block', position: 'relative', height: 'max-content', width: '100%' }}>
|
||||
{step ?
|
||||
step.type === 'instruction' ?
|
||||
<Instruction step={step} />
|
||||
: <Assessment step={step} name={name} /> // if step.type === 'assessment'
|
||||
: null}
|
||||
|
||||
<div style={{marginTop: '20px', position: 'absolute', bottom: '10px'}}>
|
||||
<Button style={{marginRight: '10px', height: '35px'}} variant='contained' disabled={this.props.activeStep === 0} onClick={() => this.props.tutorialStep(this.props.activeStep-1)}>Zurück</Button>
|
||||
<Button style={{height: '35px'}}variant='contained' color='primary' disabled={this.props.activeStep === tutorial.steps.length-1} onClick={() => this.props.tutorialStep(this.props.activeStep+1)}>Weiter</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<div style={{ marginTop: '20px', position: 'absolute', bottom: '10px' }}>
|
||||
<Button style={{ marginRight: '10px', height: '35px' }} variant='contained' disabled={this.props.activeStep === 0} onClick={() => this.props.tutorialStep(this.props.activeStep - 1)}>Zurück</Button>
|
||||
<Button style={{ height: '35px' }} variant='contained' color='primary' disabled={this.props.activeStep === tutorial.steps.length - 1} onClick={() => this.props.tutorialStep(this.props.activeStep + 1)}>Weiter</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ import clsx from 'clsx';
|
||||
|
||||
import Breadcrumbs from '../Breadcrumbs';
|
||||
|
||||
import tutorials from '../../data/tutorials.json';
|
||||
import tutorials from '../../data/tutorials';
|
||||
// import tutorials from '../../data/tutorials.json';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
@ -54,7 +55,7 @@ class TutorialHome extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Breadcrumbs content={[{link: '/tutorial', title: 'Tutorial'}]}/>
|
||||
<Breadcrumbs content={[{ link: '/tutorial', title: 'Tutorial' }]} />
|
||||
|
||||
<h1>Tutorial-Übersicht</h1>
|
||||
<Grid container spacing={2}>
|
||||
@ -62,36 +63,37 @@ class TutorialHome extends Component {
|
||||
var status = this.props.status.filter(status => status.id === tutorial.id)[0];
|
||||
var tasks = status.tasks;
|
||||
var error = status.tasks.filter(task => task.type === 'error').length > 0;
|
||||
var success = status.tasks.filter(task => task.type === 'success').length/tasks.length
|
||||
var success = status.tasks.filter(task => task.type === 'success').length / tasks.length
|
||||
var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other';
|
||||
return (
|
||||
<Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}>
|
||||
<Link to={`/tutorial/${tutorial.id}`} style={{textDecoration: 'none', color: 'inherit'}}>
|
||||
<Paper style={{height: '150px', padding: '10px', position:'relative', overflow: 'hidden'}}>
|
||||
<Link to={`/tutorial/${tutorial.id}`} style={{ textDecoration: 'none', color: 'inherit' }}>
|
||||
<Paper style={{ height: '150px', padding: '10px', position: 'relative', overflow: 'hidden' }}>
|
||||
{tutorial.title}
|
||||
<div className={clsx(this.props.classes.outerDiv)} style={{width: '160px', height: '160px', border: 0}}>
|
||||
<svg style={{width: '100%', height: '100%'}}>
|
||||
<div className={clsx(this.props.classes.outerDiv)} style={{ width: '160px', height: '160px', border: 0 }}>
|
||||
<svg style={{ width: '100%', height: '100%' }}>
|
||||
{error || success === 1 ?
|
||||
<circle className={error ? this.props.classes.outerDivError : this.props.classes.outerDivSuccess} style={{transform: 'rotate(-44deg)', transformOrigin: "50% 50%"}} r="75" cx="50%" cy="50%" fill="none" stroke-width="10"></circle>
|
||||
: <circle className={this.props.classes.outerDivOther} style={{transform: 'rotate(-44deg)', transformOrigin: "50% 50%"}} r="75" cx="50%" cy="50%" fill="none" stroke-width="10" stroke-dashoffset={`${(75*2*Math.PI)*(1-(50/100+success/2))}`} stroke-dasharray={`${(75*2*Math.PI)*(1-(50/100-success/2))} ${(75*2*Math.PI)*(1-(50/100+success/2))}`}></circle>}
|
||||
<circle className={error ? this.props.classes.outerDivError : this.props.classes.outerDivSuccess} style={{ transform: 'rotate(-44deg)', transformOrigin: "50% 50%" }} r="75" cx="50%" cy="50%" fill="none" stroke-width="10"></circle>
|
||||
: <circle className={this.props.classes.outerDivOther} style={{ transform: 'rotate(-44deg)', transformOrigin: "50% 50%" }} r="75" cx="50%" cy="50%" fill="none" stroke-width="10" stroke-dashoffset={`${(75 * 2 * Math.PI) * (1 - (50 / 100 + success / 2))}`} stroke-dasharray={`${(75 * 2 * Math.PI) * (1 - (50 / 100 - success / 2))} ${(75 * 2 * Math.PI) * (1 - (50 / 100 + success / 2))}`}></circle>}
|
||||
{success < 1 && !error ?
|
||||
<circle className={this.props.classes.outerDivSuccess} style={{transform: 'rotate(-44deg)', transformOrigin: "50% 50%"}} r="75" cx="50%" cy="50%" fill="none" stroke-width="10" stroke-dashoffset={`${(75*2*Math.PI)*(1-(50/100+success/2))}`} stroke-dasharray={`${(75*2*Math.PI)}`}>
|
||||
<circle className={this.props.classes.outerDivSuccess} style={{ transform: 'rotate(-44deg)', transformOrigin: "50% 50%" }} r="75" cx="50%" cy="50%" fill="none" stroke-width="10" stroke-dashoffset={`${(75 * 2 * Math.PI) * (1 - (50 / 100 + success / 2))}`} stroke-dasharray={`${(75 * 2 * Math.PI)}`}>
|
||||
</circle>
|
||||
: null}
|
||||
: null}
|
||||
</svg>
|
||||
</div>
|
||||
<div className={clsx(this.props.classes.outerDiv, tutorialStatus === 'Error' ? this.props.classes.outerDivError : tutorialStatus === 'Success' ? this.props.classes.outerDivSuccess : null)}>
|
||||
<div className={this.props.classes.innerDiv}>
|
||||
{error || success === 1 ?
|
||||
<FontAwesomeIcon size='4x' icon={tutorialStatus === 'Success' ? faCheck : faTimes}/>
|
||||
: <Typography variant='h3' className={success > 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success*100)}%</Typography>
|
||||
<FontAwesomeIcon size='4x' icon={tutorialStatus === 'Success' ? faCheck : faTimes} />
|
||||
: <Typography variant='h3' className={success > 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success * 100)}%</Typography>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
</Link>
|
||||
</Grid>
|
||||
)})}
|
||||
)
|
||||
})}
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
@ -108,4 +110,4 @@ const mapStateToProps = state => ({
|
||||
status: state.tutorial.status
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(withStyles(styles, {withTheme: true})(TutorialHome));
|
||||
export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(TutorialHome));
|
||||
|
48
src/data/AnzeigeVonMesswertenAufDemDisplay.json
Normal file
48
src/data/AnzeigeVonMesswertenAufDemDisplay.json
Normal file
@ -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": "<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <block type=\"arduino_functions\" id=\"QWW|$jB8+*EL;}|#uA\" deletable=\"false\" x=\"27\" y=\"16\">\n <statement name=\"SETUP_FUNC\">\n <block type=\"sensebox_display_beginDisplay\" id=\"d-~:P%TW,R%^_-f-1dxX\"></block>\n </statement>\n <statement name=\"LOOP_FUNC\">\n <block type=\"sensebox_display_show\" id=\"(5%h|:|-`y]}+8v*hn]!\">\n <statement name=\"SHOW\">\n <block type=\"sensebox_display_printDisplay\" id=\"$C*;1csr5CgR*4j]-_Xw\">\n <field name=\"COLOR\">WHITE,BLACK</field>\n <field name=\"SIZE\">1</field>\n <field name=\"X\">0</field>\n <field name=\"Y\">0</field>\n <value name=\"printDisplay\">\n <block type=\"sensebox_sensor_temp_hum\" id=\"O=5dozQ`l)HW:K5,xg1e\">\n <field name=\"NAME\">Temperature</field>\n </block>\n </value>\n </block>\n </statement>\n </block>\n </statement>\n </block>\n</xml>"
|
||||
},
|
||||
{
|
||||
"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": "<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <block type=\"arduino_functions\" id=\"QWW|$jB8+*EL;}|#uA\" x=\"27\" y=\"16\">\n <statement name=\"SETUP_FUNC\">\n <block type=\"sensebox_display_beginDisplay\" id=\"d-~:P%TW,R%^_-f-1dxX\"></block>\n </statement>\n <statement name=\"LOOP_FUNC\">\n <block type=\"sensebox_display_show\" id=\"(5%h|:|-`y]}+8v*hn]!\">\n <statement name=\"SHOW\">\n <block type=\"sensebox_display_printDisplay\" id=\"$C*;1csr5CgR*4j]-_Xw\">\n <field name=\"COLOR\">WHITE,BLACK</field>\n <field name=\"SIZE\">1</field>\n <field name=\"X\">0</field>\n <field name=\"Y\">0</field>\n <value name=\"printDisplay\">\n <block type=\"sensebox_sensor_temp_hum\" id=\"O=5dozQ`l)HW:K5,xg1e\">\n <field name=\"NAME\">Temperature</field>\n </block>\n </value>\n </block>\n </statement>\n <next>\n <block type=\"sensebox_display_clearDisplay\" id=\"]EY=d!LZThC)@QJeJvRi\"></block>\n </next>\n </block>\n </statement>\n </block>\n</xml>"
|
||||
},
|
||||
{
|
||||
"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": "<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <block type=\"arduino_functions\" id=\"QWW|$jB8+*EL;}|#uA\" x=\"27\" y=\"16\">\n <statement name=\"SETUP_FUNC\">\n <block type=\"sensebox_display_beginDisplay\" id=\"d-~:P%TW,R%^_-f-1dxX\"></block>\n </statement>\n <statement name=\"LOOP_FUNC\">\n <block type=\"sensebox_display_show\" id=\"(5%h|:|-`y]}+8v*hn]!\">\n <statement name=\"SHOW\">\n <block type=\"sensebox_display_printDisplay\" id=\"$C*;1csr5CgR*4j]-_Xw\">\n <field name=\"COLOR\">WHITE,BLACK</field>\n <field name=\"SIZE\">1</field>\n <field name=\"X\">0</field>\n <field name=\"Y\">0</field>\n <value name=\"printDisplay\">\n <block type=\"sensebox_sensor_temp_hum\" id=\"O=5dozQ`l)HW:K5,xg1e\">\n <field name=\"NAME\">Temperature</field>\n </block>\n </value>\n </block>\n </statement>\n <next>\n <block type=\"sensebox_display_clearDisplay\" id=\"T;T6Q?W~]V+*(^cr21p_\"></block>\n </next>\n </block>\n </statement>\n </block>\n</xml>"
|
||||
}
|
||||
]
|
||||
}
|
1
src/data/ErsteSchritte.json
Normal file
1
src/data/ErsteSchritte.json
Normal file
@ -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":"<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <block type=\"arduino_functions\" id=\"QWW|$jB8+*EL;}|#uA\" deletable=\"false\" x=\"27\" y=\"16\"></block>\n</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":"<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <block type=\"sensebox_led\" id=\"S_GEYN/`Z]?{5:aKp6e^\" x=\"21\" y=\"27\">\n <field name=\"PIN\">1</field>\n <field name=\"STAT\">HIGH</field>\n </block>\n</xml>"},{"id":5,"type":"task","headline":"Aufgabe 1","text":"Verwende den zuvor kennengelernten Block, um die LED zum leuchten zu bringen.","xml":"<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <block type=\"arduino_functions\" id=\"QWW|$jB8+*EL;}|#uA\" deletable=\"false\" x=\"27\" y=\"16\">\n <statement name=\"LOOP_FUNC\">\n <block type=\"sensebox_led\" id=\"S#27sNt/*c:[Zv+YyzD9\">\n <field name=\"PIN\">1</field>\n <field name=\"STAT\">HIGH</field>\n </block>\n </statement>\n </block>\n</xml>"},{"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"}}]}
|
44
src/data/Wenn-DannBedingungen.json
Normal file
44
src/data/Wenn-DannBedingungen.json
Normal file
@ -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": "<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <block type=\"arduino_functions\" id=\"QWW|$jB8+*EL;}|#uA\" x=\"27\" y=\"16\">\n <statement name=\"LOOP_FUNC\">\n <block type=\"controls_if\" id=\"yu|L-MD~uP9vg@9F}Lu(\">\n <value name=\"IF0\">\n <block type=\"logic_compare\" id=\"~P6$8C+4++}u[iIr#%2-\">\n <field name=\"OP\">EQ</field>\n <value name=\"A\">\n <block type=\"sensebox_sensor_temp_hum\" id=\"Wy!eu6l0F#5ST~!$T*^W\">\n <field name=\"NAME\">Temperature</field>\n </block>\n </value>\n </block>\n </value>\n </block>\n </statement>\n </block>\n <block type=\"math_number\" id=\"n1maf$o[.I3`ce3D}]/q\" x=\"644\" y=\"180\">\n <field name=\"NUM\">0</field>\n </block>\n</xml>"
|
||||
},
|
||||
{
|
||||
"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": "<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <block type=\"arduino_functions\" id=\"QWW|$jB8+*EL;}|#uA\" deletable=\"false\" x=\"27\" y=\"16\">\n <statement name=\"LOOP_FUNC\">\n <block type=\"controls_if\" id=\"^HaPX`(}uMFuMD~k1ao9\">\n <mutation elseif=\"1\"></mutation>\n <value name=\"IF0\">\n <block type=\"logic_compare\" id=\"|1,]aWRGs/Wp0}YQ{Ln{\">\n <field name=\"OP\">EQ</field>\n <value name=\"A\">\n <block type=\"sensebox_button\" id=\",@aRkh3M9K(DcYP$A(:[\">\n <field name=\"FUNCTION\">isPressed</field>\n <field name=\"PIN\">0</field>\n </block>\n </value>\n <value name=\"B\">\n <block type=\"logic_boolean\" id=\"ZjXsPgomX!cIM8bll!9;\">\n <field name=\"BOOL\">TRUE</field>\n </block>\n </value>\n </block>\n </value>\n <statement name=\"DO0\">\n <block type=\"sensebox_led\" id=\"fTE4,Kwz5s8uHaE/k:!h\">\n <field name=\"PIN\">1</field>\n <field name=\"STAT\">HIGH</field>\n </block>\n </statement>\n <value name=\"IF1\">\n <block type=\"logic_compare\" id=\"v6U9{L$*?0)r.8qG1$gn\">\n <field name=\"OP\">EQ</field>\n <value name=\"A\">\n <block type=\"sensebox_button\" id=\"yLypRfX$0DzgPYw8F/q#\">\n <field name=\"FUNCTION\">isPressed</field>\n <field name=\"PIN\">0</field>\n </block>\n </value>\n <value name=\"B\">\n <block type=\"logic_boolean\" id=\"r0c0q~2^GQo7DDjPv1.C\">\n <field name=\"BOOL\">FALSE</field>\n </block>\n </value>\n </block>\n </value>\n <statement name=\"DO1\">\n <block type=\"sensebox_led\" id=\"Zx0)_+JAGfG~b`.fYOIl\">\n <field name=\"PIN\">1</field>\n <field name=\"STAT\">LOW</field>\n </block>\n </statement>\n </block>\n </statement>\n </block>\n</xml>"
|
||||
}
|
||||
]
|
||||
}
|
50
src/data/loops_01.json
Normal file
50
src/data/loops_01.json
Normal file
@ -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": "<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <block type=\"controls_repeat_ext\" id=\"!|`dyF$`~*!l~D[TUc4N\" x=\"38\" y=\"32\">\n <value name=\"TIMES\">\n <block type=\"math_number\" id=\"ktgQ[7pD~M{sq;r^kLuz\">\n <field name=\"NUM\">10</field>\n </block>\n </value>\n </block>\n</xml>"
|
||||
},
|
||||
{
|
||||
"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": "<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <block type=\"arduino_functions\" id=\"QWW|$jB8+*EL;}|#uA\" deletable=\"false\" x=\"18\" y=\"18\">\n <statement name=\"SETUP_FUNC\">\n <block type=\"controls_repeat_ext\" id=\"_d{J^FWUT8M?}o[/6Fpj\">\n <value name=\"TIMES\">\n <block type=\"math_number\" id=\"qao_;lzo~kE?25HM*kJ+\">\n <field name=\"NUM\">5</field>\n </block>\n </value>\n <statement name=\"DO\">\n <block type=\"sensebox_led\" id=\"A)}4:wR_79zAHUBq5?1j\">\n <field name=\"PIN\">1</field>\n <field name=\"STAT\">HIGH</field>\n <next>\n <block type=\"time_delay\" id=\"Yfn;,|wxaRhium=T[-wM\">\n <value name=\"DELAY_TIME_MILI\">\n <block type=\"math_number\" id=\"z=1f4hMm_Q~e-+Wvh,S|\">\n <field name=\"NUM\">1000</field>\n </block>\n </value>\n <next>\n <block type=\"sensebox_led\" id=\"nu2%x%_iigf]r$FJ7XEw\">\n <field name=\"PIN\">1</field>\n <field name=\"STAT\">LOW</field>\n <next>\n <block type=\"time_delay\" id=\"4@Y4E|ewWB)([vf/4Ttn\">\n <value name=\"DELAY_TIME_MILI\">\n <block type=\"math_number\" id=\"l,ZAG8;|Uv^:P5/FOjwD\">\n <field name=\"NUM\">1000</field>\n </block>\n </value>\n </block>\n </next>\n </block>\n </next>\n </block>\n </next>\n </block>\n </statement>\n </block>\n </statement>\n </block>\n</xml>"
|
||||
},
|
||||
{
|
||||
"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": "<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <variables>\n <variable id=\")~{j#yh!vpAj!r)xZ%r`\">i</variable>\n </variables>\n <block type=\"arduino_functions\" id=\"QWW|$jB8+*EL;}|#uA\" x=\"27\" y=\"16\">\n <statement name=\"LOOP_FUNC\">\n <block type=\"controls_for\" id=\"FYj`$_+6-llMr!1pbbGa\">\n <field name=\"VAR\" id=\")~{j#yh!vpAj!r)xZ%r`\">i</field>\n <value name=\"FROM\">\n <block type=\"math_number\" id=\"d`8}:TQSpqu@$),hheqW\">\n <field name=\"NUM\">100</field>\n </block>\n </value>\n <value name=\"TO\">\n <block type=\"math_number\" id=\"SugsqbJBjnV.+wt,l*os\">\n <field name=\"NUM\">1000</field>\n </block>\n </value>\n <value name=\"BY\">\n <block type=\"math_number\" id=\"A.r{E[gVmy`GOH[/UjOd\">\n <field name=\"NUM\">100</field>\n </block>\n </value>\n <statement name=\"DO\">\n <block type=\"sensebox_led\" id=\",|)Qs}dfbh`hTL#2:vEr\">\n <field name=\"PIN\">1</field>\n <field name=\"STAT\">HIGH</field>\n <next>\n <block type=\"time_delay\" id=\"P!noJ-RN{(E{=P!1c-un\">\n <value name=\"DELAY_TIME_MILI\">\n <block type=\"variables_get_dynamic\" id=\"bd;B*4HgU:~Vb2kQ9qh.\">\n <field name=\"VAR\" id=\")~{j#yh!vpAj!r)xZ%r`\">i</field>\n </block>\n </value>\n <next>\n <block type=\"sensebox_led\" id=\"_k?%f6^b0WNYifw]yrd7\">\n <field name=\"PIN\">1</field>\n <field name=\"STAT\">LOW</field>\n <next>\n <block type=\"time_delay\" id=\"#BYOOJXBj%)0op!76)z=\">\n <value name=\"DELAY_TIME_MILI\">\n <block type=\"variables_get_dynamic\" id=\"(cg$kq?jc~Zi`6WosPN5\">\n <field name=\"VAR\" id=\")~{j#yh!vpAj!r)xZ%r`\">i</field>\n </block>\n </value>\n </block>\n </next>\n </block>\n </next>\n </block>\n </next>\n </block>\n </statement>\n </block>\n </statement>\n </block>\n</xml>"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "task",
|
||||
"headline": "Schleifen mit Laufzeitvariable",
|
||||
"text": "Verwende die Schleife mit Laufzeitvariable und beeinflusse die Blinkfrequenz mithilfe dem Wert der Variable.",
|
||||
"xml": "<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <variables>\n <variable id=\"*$)UUMo%aBec4cQTwHGx\">i</variable>\n </variables>\n <block type=\"arduino_functions\" id=\"QWW|$jB8+*EL;}|#uA\" deletable=\"false\" x=\"53\" y=\"10\">\n <statement name=\"LOOP_FUNC\">\n <block type=\"controls_for\" id=\"K(eQur,H3CCpZ4aKVehO\">\n <field name=\"VAR\" id=\"*$)UUMo%aBec4cQTwHGx\">i</field>\n <value name=\"FROM\">\n <block type=\"math_number\" id=\"J^UJb^1-SukkH7yBbynj\">\n <field name=\"NUM\">100</field>\n </block>\n </value>\n <value name=\"TO\">\n <block type=\"math_number\" id=\"azP7sh9f,v$9VH[.9BCh\">\n <field name=\"NUM\">1000</field>\n </block>\n </value>\n <value name=\"BY\">\n <block type=\"math_number\" id=\"#j`dag~sK!]D#{_;._)%\">\n <field name=\"NUM\">100</field>\n </block>\n </value>\n <statement name=\"DO\">\n <block type=\"sensebox_led\" id=\"H6+{(4h(}a[yr5w,f(`,\">\n <field name=\"PIN\">1</field>\n <field name=\"STAT\">HIGH</field>\n <next>\n <block type=\"time_delay\" id=\"vPfaX^j~7;YwT+A!y@**\">\n <value name=\"DELAY_TIME_MILI\">\n <block type=\"variables_get_dynamic\" id=\"1}5O.el5^EHJCM`,oa4-\">\n <field name=\"VAR\" id=\"*$)UUMo%aBec4cQTwHGx\">i</field>\n </block>\n </value>\n <next>\n <block type=\"sensebox_led\" id=\"TRn{}$-@^eXI?eGfWJ/{\">\n <field name=\"PIN\">1</field>\n <field name=\"STAT\">LOW</field>\n <next>\n <block type=\"time_delay\" id=\"x@]F,29zp}87PA|2+eoJ\">\n <value name=\"DELAY_TIME_MILI\">\n <block type=\"variables_get_dynamic\" id=\"WF.bP/9w[WlFoF*2;*{G\">\n <field name=\"VAR\" id=\"*$)UUMo%aBec4cQTwHGx\">i</field>\n </block>\n </value>\n </block>\n </next>\n </block>\n </next>\n </block>\n </next>\n </block>\n </statement>\n </block>\n </statement>\n </block>\n <block type=\"math_number\" id=\"!42y97D|+Vi9*DKE^5p`\" disabled=\"true\" x=\"198\" y=\"184\">\n <field name=\"NUM\">1000</field>\n </block>\n</xml>"
|
||||
}
|
||||
]
|
||||
}
|
10
src/data/tutorials.js
Normal file
10
src/data/tutorials.js
Normal file
@ -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;
|
@ -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":"<xml xmlns='https://developers.google.com/blockly/xml'><block type='arduino_functions' id='QWW|$jB8+*EL;}|#uA' deletable='false' x='27' y='16'></block></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":"<xml xmlns='https://developers.google.com/blockly/xml'><block type='arduino_functions' id='QWW|$jB8+*EL;}|#uA' deletable='false' x='27' y='16'></block></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":"<xml xmlns='https://developers.google.com/blockly/xml'><block type='arduino_functions' id='QWW|$jB8+*EL;}|#uA' deletable='false' x='27' y='16'></block></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": "<xml xmlns='https://developers.google.com/blockly/xml'><block type='sensebox_wifi' id='-!X.Ay]z1ACt!f5+Vfr8'><field name='SSID'>SSID</field><field name='Password'>Password</field></block></xml>"
|
||||
},
|
||||
{
|
||||
"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": "<xml xmlns='https://developers.google.com/blockly/xml'><block type='arduino_functions' id='QWW|$jB8+*EL;}|#uA' deletable='false' x='27' y='16'><statement name='SETUP_FUNC'><block type='sensebox_wifi' id='W}P2Y^g,muH@]|@anou}'><field name='SSID'>SSID</field><field name='Password'>Password</field></block></statement></block></xml>"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "task",
|
||||
"headline": "Aufgabe 2",
|
||||
"text": "Versuche das gleiche einfach nochmal. Übung macht den Meister! ;)",
|
||||
"xml": "<xml xmlns='https://developers.google.com/blockly/xml'><block type='arduino_functions' id='QWW|$jB8+*EL;}|#uA' deletable='false' x='27' y='16'><statement name='SETUP_FUNC'><block type='sensebox_wifi' id='W}P2Y^g,muH@]|@anou}'><field name='SSID'>SSID</field><field name='Password'>Password</field></block></statement></block></xml>"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
41
src/data/variablen_01.json
Normal file
41
src/data/variablen_01.json
Normal file
@ -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": "<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <variables>\n <variable type=\"float\" id=\"s/vc$u-EyvVr.Nj(m}Qu\">Temperatur</variable>\n </variables>\n <block type=\"arduino_functions\" id=\"QWW|$jB8+*EL;}|#uA\" x=\"27\" y=\"16\">\n <statement name=\"SETUP_FUNC\">\n <block type=\"sensebox_display_beginDisplay\" id=\"`tj@e,u63(d3fs/K71`A\"></block>\n </statement>\n <statement name=\"LOOP_FUNC\">\n <block type=\"variables_set_dynamic\" id=\"9.`Dj,eU7L15ZD{o2c^{\">\n <field name=\"VAR\" id=\"s/vc$u-EyvVr.Nj(m}Qu\" variabletype=\"float\">Temperatur</field>\n <value name=\"VALUE\">\n <block type=\"sensebox_sensor_temp_hum\" id=\"Ml7-3urP[?9L4k71u5/f\">\n <field name=\"NAME\">Temperature</field>\n </block>\n </value>\n <next>\n <block type=\"sensebox_display_show\" id=\"J[dD~4AnHRXYq3qK5^{z\">\n <statement name=\"SHOW\">\n <block type=\"sensebox_display_printDisplay\" id=\"sg25UA3mp72*zt_.hgR%\">\n <field name=\"COLOR\">WHITE,BLACK</field>\n <field name=\"SIZE\">1</field>\n <field name=\"X\">0</field>\n <field name=\"Y\">0</field>\n </block>\n </statement>\n </block>\n </next>\n </block>\n </statement>\n </block>\n <block type=\"variables_get_dynamic\" id=\"Pi0a=LwOd]Qx1}q[QljS\" x=\"252\" y=\"336\">\n <field name=\"VAR\" id=\"s/vc$u-EyvVr.Nj(m}Qu\" variabletype=\"float\">Temperatur</field>\n </block>\n</xml>"
|
||||
},
|
||||
{
|
||||
"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": "<xml xmlns=\"https://developers.google.com/blockly/xml\">\n <variables>\n <variable type=\"float\" id=\";%_Xz2^W|D@`pj|28,!5\">Temperatur</variable>\n <variable type=\"float\" id=\"[EbZeJmVDy_yf%nn}kg`\">Luftfeuchte</variable>\n </variables>\n <block type=\"arduino_functions\" id=\"QWW|$jB8+*EL;}|#uA\" deletable=\"false\" x=\"27\" y=\"16\">\n <statement name=\"SETUP_FUNC\">\n <block type=\"sensebox_display_beginDisplay\" id=\"#v)|Z-SGAvCfh+tW*u:b\"></block>\n </statement>\n <statement name=\"LOOP_FUNC\">\n <block type=\"variables_set_dynamic\" id=\"4(3O[5o69|]D[1(`OS77\">\n <field name=\"VAR\" id=\";%_Xz2^W|D@`pj|28,!5\" variabletype=\"float\">Temperatur</field>\n <value name=\"VALUE\">\n <block type=\"sensebox_sensor_temp_hum\" id=\"lqo)Q_WfPH/)#n?Kejw9\">\n <field name=\"NAME\">Temperature</field>\n </block>\n </value>\n <next>\n <block type=\"variables_set_dynamic\" id=\"wZ([1QpBf,W-+ML8$yE^\">\n <field name=\"VAR\" id=\"[EbZeJmVDy_yf%nn}kg`\" variabletype=\"float\">Luftfeuchte</field>\n <value name=\"VALUE\">\n <block type=\"sensebox_sensor_temp_hum\" id=\"N@rK(BhH:*:,7?gd0znv\">\n <field name=\"NAME\">Temperature</field>\n </block>\n </value>\n <next>\n <block type=\"sensebox_display_show\" id=\")HB9?-!GebOtxFJ*UF9:\">\n <statement name=\"SHOW\">\n <block type=\"sensebox_display_printDisplay\" id=\"U90y}?~Cvp?uS2KfL#Ph\">\n <field name=\"COLOR\">WHITE,BLACK</field>\n <field name=\"SIZE\">1</field>\n <field name=\"X\">0</field>\n <field name=\"Y\">0</field>\n <value name=\"printDisplay\">\n <block type=\"variables_get_dynamic\" id=\"+.p4#smC?kl[1{cjt70R\">\n <field name=\"VAR\" id=\"[EbZeJmVDy_yf%nn}kg`\" variabletype=\"float\">Luftfeuchte</field>\n </block>\n </value>\n <next>\n <block type=\"sensebox_display_printDisplay\" id=\"GfS)4y,Y7[nm[KUW*uGL\">\n <field name=\"COLOR\">WHITE,BLACK</field>\n <field name=\"SIZE\">1</field>\n <field name=\"X\">0</field>\n <field name=\"Y\">20</field>\n <value name=\"printDisplay\">\n <block type=\"variables_get_dynamic\" id=\"wWCpBCPp9i%M13l9H^Et\">\n <field name=\"VAR\" id=\";%_Xz2^W|D@`pj|28,!5\" variabletype=\"float\">Temperatur</field>\n </block>\n </value>\n </block>\n </next>\n </block>\n </statement>\n <next>\n <block type=\"sensebox_display_clearDisplay\" id=\"E%fqa!5WcG$MDFM..7)G\"></block>\n </next>\n </block>\n </next>\n </block>\n </next>\n </block>\n </statement>\n </block>\n</xml>"
|
||||
}
|
||||
]
|
||||
}
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user