diff --git a/README.md b/README.md
index 54ef094..5b7a213 100644
--- a/README.md
+++ b/README.md
@@ -1,68 +1,28 @@
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
+
-## Available Scripts
+# React Ardublockly
-In the project directory, you can run:
+This repository contains the source code and documentation of [sensebox-ardublockly](https://sensebox-ardublockly.netlify.app/).
-### `npm start`
+This project was created with [Create React App](https://github.com/facebook/create-react-app) and represents the continuation or improvement of [blockly.sensebox.de](https://blockly.sensebox.de/ardublockly/?lang=de&board=sensebox-mcu).
-Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
-The page will reload if you make edits.
-You will also see any lint errors in the console.
+## Getting Started
-### `npm test`
+1. [Download](https://github.com/sensebox/React-Ardublockly/archive/master.zip) or clone the GitHub Repository ``git clone https://github.com/sensebox/React-Ardublockly`` and checkout to branch ``master``.
-Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
+2. install [Node.js v10.xx](https://nodejs.org/en/) on your local machine
-### `npm run build`
+3. open shell and navigate inside folder ``React-Ardublockly``
+ * run ``npm install``
+ * run ``npm start``
+4. open [localhost:3000](http://localhost:3000)
-Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance.
+## Troubleshoot
+Ensure that line 14 in [store.js](https://github.com/sensebox/React-Ardublockly/blob/master/src/store.js#L14) is commented out or otherwise you have installed [Redux DevTools Extension](http://extension.remotedev.io/).
-The build is minified and the filenames include the hashes.
-Your app is ready to be deployed!
-
-See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
-
-### `npm run eject`
-
-**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
-
-If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
-
-Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
-
-You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
-
-## Learn More
-
-You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
-
-To learn React, check out the [React documentation](https://reactjs.org/).
-
-### Code Splitting
-
-This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
-
-### Analyzing the Bundle Size
-
-This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
-
-### Making a Progressive Web App
-
-This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
-
-### Advanced Configuration
-
-This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
-
-### Deployment
-
-This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
-
-### `npm run build` fails to minify
-
-This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
+## Demo
+A demo of the current status of the master branch can be accessed via [sensebox-ardublockly.netlify.app](https://sensebox-ardublockly.netlify.app/) :rocket:.
+* [Home](https://sensebox-ardublockly.netlify.app/)
+* [Tutorial Overview](https://sensebox-ardublockly.netlify.app/tutorial)
+* [Tutorial-Builder](https://sensebox-ardublockly.netlify.app/tutorial/builder)
diff --git a/package.json b/package.json
index d241d76..a7a911e 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"@testing-library/user-event": "^7.2.1",
"blockly": "^3.20200924.0",
"file-saver": "^2.0.2",
+ "moment": "^2.28.0",
"prismjs": "^1.20.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
diff --git a/public/_redirects b/public/_redirects
new file mode 100644
index 0000000..ad37e2c
--- /dev/null
+++ b/public/_redirects
@@ -0,0 +1 @@
+/* /index.html 200
diff --git a/public/media/hardware/resistor.png b/public/media/hardware/resistor.png
deleted file mode 100644
index e6f1458..0000000
Binary files a/public/media/hardware/resistor.png and /dev/null differ
diff --git a/src/actions/tutorialActions.js b/src/actions/tutorialActions.js
index b741c3e..c902f70 100644
--- a/src/actions/tutorialActions.js
+++ b/src/actions/tutorialActions.js
@@ -1,6 +1,6 @@
import { TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_ID, TUTORIAL_STEP } from './types';
-import tutorials from '../components/Tutorial/tutorials.json';
+import tutorials from '../data/tutorials.json';
export const tutorialChange = () => (dispatch) => {
dispatch({
diff --git a/src/actions/tutorialBuilderActions.js b/src/actions/tutorialBuilderActions.js
new file mode 100644
index 0000000..f200cf0
--- /dev/null
+++ b/src/actions/tutorialBuilderActions.js
@@ -0,0 +1,263 @@
+import { PROGRESS, JSON_STRING, BUILDER_CHANGE, BUILDER_ERROR, BUILDER_TITLE, BUILDER_ID, BUILDER_ADD_STEP, BUILDER_DELETE_STEP, BUILDER_CHANGE_STEP, BUILDER_CHANGE_ORDER, BUILDER_DELETE_PROPERTY } from './types';
+
+import data from '../data/hardware.json';
+
+export const changeTutorialBuilder = () => (dispatch) => {
+ dispatch({
+ type: BUILDER_CHANGE
+ });
+};
+
+export const jsonString = (json) => (dispatch) => {
+ dispatch({
+ type: JSON_STRING,
+ payload: json
+ });
+};
+
+export const tutorialTitle = (title) => (dispatch) => {
+ dispatch({
+ type: BUILDER_TITLE,
+ payload: title
+ });
+ dispatch(changeTutorialBuilder());
+};
+
+export const tutorialSteps = (steps) => (dispatch) => {
+ dispatch({
+ type: BUILDER_ADD_STEP,
+ payload: steps
+ });
+ dispatch(changeTutorialBuilder());
+};
+
+export const tutorialId = (id) => (dispatch) => {
+ dispatch({
+ type: BUILDER_ID,
+ payload: id
+ });
+ dispatch(changeTutorialBuilder());
+};
+
+export const addStep = (index) => (dispatch, getState) => {
+ var steps = getState().builder.steps;
+ var step = {
+ id: index+1,
+ type: 'instruction',
+ headline: '',
+ text: ''
+ };
+ steps.splice(index, 0, step);
+ dispatch({
+ type: BUILDER_ADD_STEP,
+ payload: steps
+ });
+ dispatch(addErrorStep(index));
+ dispatch(changeTutorialBuilder());
+};
+
+export const addErrorStep = (index) => (dispatch, getState) => {
+ var error = getState().builder.error;
+ error.steps.splice(index, 0, {});
+ dispatch({
+ type: BUILDER_ERROR,
+ payload: error
+ });
+};
+
+export const removeStep = (index) => (dispatch, getState) => {
+ var steps = getState().builder.steps;
+ steps.splice(index, 1);
+ dispatch({
+ type: BUILDER_DELETE_STEP,
+ payload: steps
+ });
+ dispatch(removeErrorStep(index));
+ dispatch(changeTutorialBuilder());
+};
+
+export const removeErrorStep = (index) => (dispatch, getState) => {
+ var error = getState().builder.error;
+ error.steps.splice(index, 1);
+ dispatch({
+ type: BUILDER_ERROR,
+ payload: error
+ });
+};
+
+export const changeContent = (index, property, content) => (dispatch, getState) => {
+ var steps = getState().builder.steps;
+ var step = steps[index];
+ step[property] = content;
+ dispatch({
+ type: BUILDER_CHANGE_STEP,
+ payload: steps
+ });
+ dispatch(changeTutorialBuilder());
+};
+
+export const deleteProperty = (index, property) => (dispatch, getState) => {
+ var steps = getState().builder.steps;
+ var step = steps[index];
+ delete step[property];
+ dispatch({
+ type: BUILDER_DELETE_PROPERTY,
+ payload: steps
+ });
+ dispatch(changeTutorialBuilder());
+};
+
+export const changeStepIndex = (fromIndex, toIndex) => (dispatch, getState) => {
+ var steps = getState().builder.steps;
+ var step = steps[fromIndex];
+ steps.splice(fromIndex, 1);
+ steps.splice(toIndex, 0, step);
+ dispatch({
+ type: BUILDER_CHANGE_ORDER,
+ payload: steps
+ });
+ dispatch(changeErrorStepIndex(fromIndex, toIndex));
+ dispatch(changeTutorialBuilder());
+};
+
+export const changeErrorStepIndex = (fromIndex, toIndex) => (dispatch, getState) => {
+ var error = getState().builder.error;
+ var errorStep = error.steps[fromIndex];
+ error.steps.splice(fromIndex, 1);
+ error.steps.splice(toIndex, 0, errorStep);
+ dispatch({
+ type: BUILDER_ERROR,
+ payload: error
+ });
+};
+
+export const setError = (index, property) => (dispatch, getState) => {
+ var error = getState().builder.error;
+ if(index !== undefined){
+ error.steps[index][property] = true;
+ }
+ else {
+ error[property] = true;
+ }
+ dispatch({
+ type: BUILDER_ERROR,
+ payload: error
+ });
+ dispatch(changeTutorialBuilder());
+};
+
+export const deleteError = (index, property) => (dispatch, getState) => {
+ var error = getState().builder.error;
+ if(index !== undefined){
+ delete error.steps[index][property];
+ }
+ else {
+ delete error[property];
+ }
+ dispatch({
+ type: BUILDER_ERROR,
+ payload: error
+ });
+ dispatch(changeTutorialBuilder());
+};
+
+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 === ''){
+ dispatch(setError(undefined, 'title'));
+ }
+ var type = builder.steps.map((step, i) => {
+ 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(i, 'requirements', requirements));
+ }
+ }
+ if(step.hardware === undefined || step.hardware.length < 1){
+ dispatch(setError(i, 'hardware'));
+ }
+ else{
+ var hardwareIds = data.map(hardware => hardware.id);
+ var hardware = step.hardware.filter(hardware => hardwareIds.includes(hardware));
+ if(hardware.length < step.hardware.length){
+ dispatch(changeContent(i, 'hardware', hardware));
+ }
+ }
+ }
+ if(step.headline === undefined || step.headline === ''){
+ dispatch(setError(i, 'headline'));
+ }
+ 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'));
+ }
+};
+
+
+export const checkError = () => (dispatch, getState) => {
+ dispatch(setSubmitError());
+ var error = getState().builder.error;
+ 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){
+ return true
+ }
+ }
+ return false;
+}
+
+export const progress = (inProgress) => (dispatch) => {
+ dispatch({
+ type: PROGRESS,
+ payload: inProgress
+ })
+};
+
+export const resetTutorial = () => (dispatch, getState) => {
+ dispatch(jsonString(''));
+ dispatch(tutorialTitle(''));
+ dispatch(tutorialId(''));
+ var steps = [
+ {
+ id: 1,
+ type: 'instruction',
+ headline: '',
+ text: '',
+ hardware: [],
+ requirements: []
+ }
+ ];
+ dispatch(tutorialSteps(steps));
+ dispatch({
+ type: BUILDER_ERROR,
+ payload: {
+ steps: [{}]
+ }
+ });
+};
+
+export const readJSON = (json) => (dispatch, getState) => {
+ dispatch(resetTutorial());
+ dispatch({
+ type: BUILDER_ERROR,
+ payload: {
+ steps: json.steps.map(() => {return {};})
+ }
+ });
+ dispatch(tutorialTitle(json.title));
+ dispatch(tutorialId(json.id));
+ dispatch(tutorialSteps(json.steps));
+ dispatch(setSubmitError());
+ dispatch(progress(false));
+};
diff --git a/src/actions/types.js b/src/actions/types.js
index a086a29..0a1dc72 100644
--- a/src/actions/types.js
+++ b/src/actions/types.js
@@ -14,3 +14,16 @@ export const TUTORIAL_CHANGE = 'TUTORIAL_CHANGE';
export const TUTORIAL_XML = 'TUTORIAL_XML';
export const TUTORIAL_ID = 'TUTORIAL_ID';
export const TUTORIAL_STEP = 'TUTORIAL_STEP';
+export const JSON_STRING = 'JSON_STRING';
+
+
+export const BUILDER_CHANGE = 'BUILDER_CHANGE';
+export const BUILDER_TITLE = 'BUILDER_TITLE';
+export const BUILDER_ID = 'BUILDER_ID';
+export const BUILDER_ADD_STEP = 'BUILDER_ADD_STEP';
+export const BUILDER_DELETE_STEP = 'BUILDER_DELETE_STEP';
+export const BUILDER_CHANGE_STEP = 'BUILDER_CHANGE_STEP';
+export const BUILDER_CHANGE_ORDER = 'BUILDER_CHANGE_ORDER';
+export const BUILDER_DELETE_PROPERTY = 'BUILDER_DELETE_PROPERTY';
+export const BUILDER_ERROR = 'BUILDER_ERROR';
+export const PROGRESS = 'PROGRESS';
diff --git a/src/components/Blockly/BlocklyComponent.css b/src/components/Blockly/BlocklyComponent.css
deleted file mode 100644
index 8705fbc..0000000
--- a/src/components/Blockly/BlocklyComponent.css
+++ /dev/null
@@ -1,7 +0,0 @@
-#blocklyDiv {
- height: 100%;
- min-height: 500px;
- width: 100%;
- /* border: 1px solid #4EAF47; */
- position: relative;
-}
diff --git a/src/components/Blockly/BlocklyComponent.jsx b/src/components/Blockly/BlocklyComponent.jsx
index 048e9c8..b025821 100644
--- a/src/components/Blockly/BlocklyComponent.jsx
+++ b/src/components/Blockly/BlocklyComponent.jsx
@@ -22,7 +22,6 @@
*/
import React from 'react';
-import './BlocklyComponent.css';
import Blockly from 'blockly/core';
import locale from 'blockly/msg/en';
diff --git a/src/components/Blockly/BlocklySvg.js b/src/components/Blockly/BlocklySvg.js
new file mode 100644
index 0000000..174b7c3
--- /dev/null
+++ b/src/components/Blockly/BlocklySvg.js
@@ -0,0 +1,65 @@
+import React, { Component } from 'react';
+
+import * as Blockly from 'blockly/core';
+
+class BlocklySvg extends Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ svg: ''
+ };
+ }
+
+ componentDidMount() {
+ this.getSvg();
+ }
+
+ componentDidUpdate(props) {
+ if(props.initialXml !== this.props.initialXml){
+ this.getSvg();
+ }
+ }
+
+ getSvg = () => {
+ const workspace = Blockly.getMainWorkspace();
+ workspace.clear();
+ Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(this.props.initialXml), workspace);
+ var canvas = workspace.svgBlockCanvas_.cloneNode(true);
+
+ if (canvas.children[0] !== undefined) {
+ canvas.removeAttribute("transform");
+
+ // does not work in react
+ // var cssContent = Blockly.Css.CONTENT.join('');
+ var cssContent = '';
+ for (var i = 0; i < document.getElementsByTagName('style').length; i++) {
+ if(/^blockly.*$/.test(document.getElementsByTagName('style')[i].id)){
+ cssContent += document.getElementsByTagName('style')[i].firstChild.data.replace(/\..* \./g, '.');
+ }
+ }
+
+ var css = '