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