Merge pull request #142 from sensebox/development

Development
This commit is contained in:
Mario Pesch 2022-10-21 11:04:12 +02:00 committed by GitHub
commit 5b43a069f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 4661 additions and 1554 deletions

2
.env
View File

@ -2,5 +2,7 @@ REACT_APP_COMPILER_URL=https://compiler.sensebox.de
REACT_APP_BOARD=sensebox-mcu
REACT_APP_BLOCKLY_API=https://api.blockly.sensebox.de
GENERATE_SOURCEMAP=false
# in days
REACT_APP_SHARE_LINK_EXPIRES=30

2
.gitignore vendored
View File

@ -22,4 +22,4 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
package-lock.json

View File

@ -1,45 +1,58 @@
{
"name": "blockly-react",
"version": "0.1.0",
"version": "1.0.0",
"private": true,
"dependencies": {
"@blockly/block-plus-minus": "^2.0.10",
"@blockly/field-grid-dropdown": "^1.0.25",
"@blockly/field-slider": "^2.1.1",
"@blockly/plugin-scroll-options": "^1.0.2",
"@blockly/plugin-typed-variable-modal": "^3.1.26",
"@blockly/zoom-to-fit": "^2.0.7",
"@fortawesome/fontawesome-svg-core": "^1.2.30",
"@fortawesome/free-solid-svg-icons": "^5.14.0",
"@fortawesome/react-fontawesome": "^0.1.11",
"@material-ui/core": "^4.11.0",
"@sentry/react": "^6.0.0",
"@sentry/tracing": "^6.0.0",
"@blockly/block-plus-minus": "^3.0.5",
"@blockly/field-grid-dropdown": "^1.0.31",
"@blockly/field-slider": "^3.0.5",
"@blockly/plugin-scroll-options": "^2.0.5",
"@blockly/plugin-typed-variable-modal": "^4.0.5",
"@blockly/workspace-backpack": "^2.0.12",
"@blockly/zoom-to-fit": "^2.0.14",
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.1.19",
"@material-ui/core": "^4.12.4",
"@monaco-editor/react": "^4.3.1",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^7.2.1",
"axios": "^0.22.0",
"blockly": "^6.20210701.0",
"file-saver": "^2.0.2",
"blockly": "^8.0.3",
"file-saver": "^2.0.5",
"markdown-it": "^12.3.2",
"mnemonic-id": "^3.2.7",
"moment": "^2.28.0",
"moment": "^2.29.4",
"prismjs": "^1.27.0",
"qrcode.react": "^3.1.0",
"react": "^17.0.2",
"react-cookie-consent": "^7.0.0",
"react-cookie-consent": "^7.2.1",
"react-dom": "^17.0.2",
"react-markdown": "^5.0.2",
"react-markdown": "^8.0.0",
"react-markdown-editor-lite": "^1.3.3",
"react-mde": "^11.5.0",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"react-scripts": "^4.0.3",
"reactour": "^1.18.0",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"react-rating-stars-component": "^2.2.0",
"react-redux": "^7.2.9",
"react-router-dom": "^5.3.3",
"react-scripts": "^5.0.0",
"react-share": "^4.4.0",
"react-spinners": "^0.13.3",
"reactour": "^1.18.7",
"redux": "^4.2.0",
"redux-thunk": "^2.4.1",
"remark-gemoji": "^7.0.1",
"remark-gfm": "^3.0.1",
"styled-components": "^4.4.1",
"uuid": "^8.3.1"
"uuid": "^8.3.1",
"watchpack": "^2.3.1"
},
"resolutions": {
"//": "See https://github.com/facebook/create-react-app/issues/11773",
"react-error-overlay": "6.0.9"
},
"scripts": {
"start": "react-scripts start",
"start": "node_modules/react-scripts/bin/react-scripts.js start",
"dev": "set \"REACT_APP_BLOCKLY_API=http://localhost:8080\" && npm start",
"build": "react-scripts build",
"test": "react-scripts test",
@ -48,16 +61,9 @@
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
"browserslist": [
">0.2%",
"not dead",
"not op_mini all"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

View File

@ -1,51 +1,64 @@
.wrapper {
min-height: calc(100vh - 60px); /* will cover the 100% of viewport - height of footer (padding-bottom) */
min-height: calc(
100vh - 60px
); /* will cover the 100% of viewport - height of footer (padding-bottom) */
overflow: hidden;
display: block;
position: relative;
padding-bottom: 60px; /* height of your footer + 30px*/
}
.tutorial img{
.tutorial img {
display: flex;
align-items: center;
max-height: 40vH;
max-height: 40vh;
max-width: 100%;
margin: auto;
}
.news img{
display: flex;
align-items: center;
max-height: 40vH;
max-width: 100%;
margin: auto;
}
.tutorial blockquote{
background: #f9f9f9;
border-left: 10px solid#4EAF47;
margin: 1.5em 10px;
padding: 0.5em 10px;
quotes: "\201C""\201D""\2018""\2019";
}
blockquote:before {
color:#4EAF47;
content: open-quote;
font-size: 4em;
line-height: 0.1em;
margin-right: 0.25em;
vertical-align: -0.4em;
}
blockquote p {
display: inline;
}
.overlay {
display: flex;
flex-direction: column;
align-items: center;
}
.news img {
display: flex;
align-items: center;
max-height: 40vh;
max-width: 100%;
margin: auto;
}
.tutorial blockquote {
background: #f9f9f9;
border-left: 10px solid#4EAF47;
margin: 1.5em 10px;
padding: 0.5em 10px;
quotes: "\201C""\201D""\2018""\2019";
}
blockquote:before {
color: #4eaf47;
content: open-quote;
font-size: 4em;
line-height: 0.1em;
margin-right: 0.25em;
vertical-align: -0.4em;
}
blockquote p {
display: inline;
}
.tutorial table,
th,
td {
border: 1px solid #ddd;
}
.tutorial th {
padding-top: 12px;
padding-bottom: 12px;
text-align: left;
background-color: #4eaf47;
color: white;
}
.overlay {
display: flex;
flex-direction: column;
align-items: center;
}

View File

@ -1,35 +1,34 @@
import React, { Component } from 'react';
import React, { Component } from "react";
import { BrowserRouter as Router } from 'react-router-dom';
import { Router } from "react-router-dom";
import { createBrowserHistory } from "history";
import { Provider } from 'react-redux';
import store from './store';
import { loadUser } from './actions/authActions';
import { Provider } from "react-redux";
import store from "./store";
import { loadUser } from "./actions/authActions";
import './App.css';
import "./App.css";
import { ThemeProvider, createMuiTheme } from '@material-ui/core/styles';
import { ThemeProvider, createTheme } from "@material-ui/core/styles";
import Content from './components/Content';
import Content from "./components/Content";
const theme = createMuiTheme({
const theme = createTheme({
palette: {
primary: {
main: '#4EAF47',
contrastText: '#ffffff'
main: "#4EAF47",
contrastText: "#ffffff",
},
secondary: {
main: '#DDDDDD'
main: "#DDDDDD",
},
button: {
compile: '#e27136'
}
}
compile: "#e27136",
},
},
});
class App extends Component {
componentDidMount() {
store.dispatch(loadUser());
}

View File

@ -63,63 +63,56 @@ export const loadUser = () => (dispatch) => {
});
};
var logoutTimerId;
const timeToLogout = 14.9 * 60 * 1000; // nearly 15 minutes corresponding to the API
// Login user
export const login =
({ email, password }) =>
(dispatch) => {
dispatch({
type: USER_LOADING,
});
// Headers
const config = {
headers: {
"Content-Type": "application/json",
},
};
// Request Body
const body = JSON.stringify({ email, password });
axios
.post(`${process.env.REACT_APP_BLOCKLY_API}/user`, body, config)
.then((res) => {
// Logout automatically if refreshToken "expired"
const logoutTimer = () =>
setTimeout(() => dispatch(logout()), timeToLogout);
logoutTimerId = logoutTimer();
dispatch(setLanguage(res.data.user.language));
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
dispatch({
type: GET_STATUS,
payload: res.data.user.status,
});
dispatch(returnSuccess(res.data.message, res.status, "LOGIN_SUCCESS"));
})
.catch((err) => {
dispatch(
returnErrors(
err.response.data.message,
err.response.status,
"LOGIN_FAIL"
)
);
dispatch({
type: LOGIN_FAIL,
});
var status = [];
if (window.localStorage.getItem("status")) {
status = JSON.parse(window.localStorage.getItem("status"));
}
dispatch({
type: GET_STATUS,
payload: status,
});
(dispatch) => {
dispatch({
type: USER_LOADING,
});
};
// Headers
const config = {
headers: {
"Content-Type": "application/json",
},
};
// Request Body
const body = JSON.stringify({ email, password });
axios
.post(`${process.env.REACT_APP_BLOCKLY_API}/user`, body, config)
.then((res) => {
dispatch(setLanguage(res.data.user.language));
dispatch({
type: LOGIN_SUCCESS,
payload: res.data,
});
dispatch({
type: GET_STATUS,
payload: res.data.user.status,
});
dispatch(returnSuccess(res.data.message, res.status, "LOGIN_SUCCESS"));
})
.catch((err) => {
dispatch(
returnErrors(
err.response.data.message,
err.response.status,
"LOGIN_FAIL"
)
);
dispatch({
type: LOGIN_FAIL,
});
var status = [];
if (window.localStorage.getItem("status")) {
status = JSON.parse(window.localStorage.getItem("status"));
}
dispatch({
type: GET_STATUS,
payload: status,
});
});
};
// Logout User
export const logout = () => (dispatch) => {
@ -144,7 +137,6 @@ export const logout = () => (dispatch) => {
}
dispatch(setLanguage(locale));
dispatch(returnSuccess(res.data.message, res.status, "LOGOUT_SUCCESS"));
clearTimeout(logoutTimerId);
},
error: (err) => {
dispatch(
@ -165,7 +157,6 @@ export const logout = () => (dispatch) => {
type: GET_STATUS,
payload: status,
});
clearTimeout(logoutTimerId);
},
};
axios
@ -222,10 +213,6 @@ export const authInterceptor = () => (dispatch, getState) => {
})
.then((res) => {
if (res.status === 200) {
clearTimeout(logoutTimerId);
const logoutTimer = () =>
setTimeout(() => dispatch(logout()), timeToLogout);
logoutTimerId = logoutTimer();
dispatch({
type: REFRESH_TOKEN_SUCCESS,
payload: res.data,

View File

@ -0,0 +1,10 @@
import {
BOARD,
} from "./types";
export const setBoard = (board) => (dispatch) => {
dispatch({
type: BOARD,
payload: board,
});
};

View File

@ -84,6 +84,77 @@ export const getTutorials = () => (dispatch, getState) => {
});
};
export const getAllTutorials = () => (dispatch, getState) => {
axios
.get(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/getAllTutorials`)
.then((res) => {
var tutorials = res.data.tutorials;
existingTutorials(tutorials, getState().tutorial.status).then(
(status) => {
dispatch({
type: TUTORIAL_SUCCESS,
payload: status,
});
dispatch(updateStatus(status));
dispatch({
type: GET_TUTORIALS,
payload: tutorials,
});
dispatch({ type: TUTORIAL_PROGRESS });
dispatch(returnSuccess(res.data.message, res.status));
}
);
})
.catch((err) => {
if (err.response) {
dispatch(
returnErrors(
err.response.data.message,
err.response.status,
"GET_TUTORIALS_FAIL"
)
);
}
dispatch({ type: TUTORIAL_PROGRESS });
});
};
export const getUserTutorials = () => (dispatch, getState) => {
axios
.get(`${process.env.REACT_APP_BLOCKLY_API}/tutorial/getUserTutorials`)
.then((res) => {
var tutorials = res.data.tutorials;
existingTutorials(tutorials, getState().tutorial.status).then(
(status) => {
dispatch({
type: TUTORIAL_SUCCESS,
payload: status,
});
dispatch(updateStatus(status));
dispatch({
type: GET_TUTORIALS,
payload: tutorials,
});
dispatch({ type: TUTORIAL_PROGRESS });
dispatch(returnSuccess(res.data.message, res.status));
}
);
})
.catch((err) => {
console.log(err);
if (err.response) {
dispatch(
returnErrors(
err.response.data.message,
err.response.status,
"GET_TUTORIALS_FAIL"
)
);
}
dispatch({ type: TUTORIAL_PROGRESS });
});
};
export const updateStatus = (status) => (dispatch, getState) => {
if (getState().auth.isAuthenticated) {
// update user account in database - sync with redux store

View File

@ -4,6 +4,9 @@ import {
BUILDER_CHANGE,
BUILDER_ERROR,
BUILDER_TITLE,
BUILDER_PUBLIC,
BUILDER_DIFFICULTY,
BUILDER_REVIEW,
BUILDER_ID,
BUILDER_ADD_STEP,
BUILDER_DELETE_STEP,
@ -35,6 +38,30 @@ export const tutorialTitle = (title) => (dispatch) => {
dispatch(changeTutorialBuilder());
};
export const tutorialPublic = (pub) => (dispatch) => {
dispatch({
type: BUILDER_PUBLIC,
payload: pub,
});
dispatch(changeTutorialBuilder());
};
export const tutorialDifficulty = (difficulty) => (dispatch) => {
dispatch({
type: BUILDER_DIFFICULTY,
payload: difficulty,
});
dispatch(changeTutorialBuilder());
};
export const tutorialReview = (review) => (dispatch) => {
dispatch({
type: BUILDER_REVIEW,
payload: review,
});
dispatch(changeTutorialBuilder());
};
export const tutorialSteps = (steps) => (dispatch) => {
dispatch({
type: BUILDER_ADD_STEP,
@ -320,6 +347,7 @@ export const readJSON = (json) => (dispatch, getState) => {
return object;
});
dispatch(tutorialTitle(json.title));
dispatch(tutorialDifficulty(json.difficulty));
dispatch(tutorialSteps(steps));
dispatch(setSubmitError());
dispatch(progress(false));

View File

@ -21,6 +21,7 @@ export const NAME = "NAME";
export const TUTORIAL_PROGRESS = "TUTORIAL_PROGRESS";
export const GET_TUTORIAL = "GET_TUTORIAL";
export const GET_TUTORIALS = "GET_TUTORIALS";
export const GET_USERTUTORIALS = "GET_USERTUTORIALS";
export const GET_STATUS = "GET_STATUS";
export const TUTORIAL_SUCCESS = "TUTORIAL_SUCCESS";
export const TUTORIAL_ERROR = "TUTORIAL_ERROR";
@ -32,6 +33,9 @@ export const JSON_STRING = "JSON_STRING";
export const BUILDER_CHANGE = "BUILDER_CHANGE";
export const BUILDER_TITLE = "BUILDER_TITLE";
export const BUILDER_DIFFICULTY = "BUILDER_DIFFICULTY";
export const BUILDER_PUBLIC = "BUILDER_PUBLIC";
export const BUILDER_REVIEW = "BUILDER_REVIEW";
export const BUILDER_ID = "BUILDER_ID";
export const BUILDER_ADD_STEP = "BUILDER_ADD_STEP";
export const BUILDER_DELETE_STEP = "BUILDER_DELETE_STEP";
@ -59,3 +63,6 @@ export const GET_PROJECT = "GET_PROJECT";
export const GET_PROJECTS = "GET_PROJECTS";
export const PROJECT_TYPE = "PROJECT_TYPE";
export const PROJECT_DESCRIPTION = "PROJECT_DESCRIPTION";
//board
export const BOARD = "BOARD";

View File

@ -1,34 +1,29 @@
import React, { Component } from 'react';
import React, { Component } from "react";
import { withStyles } from '@material-ui/core/styles';
import { fade } from '@material-ui/core/styles/colorManipulator';
import { withStyles } from "@material-ui/core/styles";
import { alpha } from "@material-ui/core/styles";
import Typography from '@material-ui/core/Typography';
import Typography from "@material-ui/core/Typography";
const styles = (theme) => ({
alert: {
marginBottom: '20px',
marginBottom: "20px",
border: `1px solid ${theme.palette.primary.main}`,
padding: '10px 20px',
borderRadius: '4px',
background: fade(theme.palette.primary.main, 0.3),
color: 'rgb(70,70,70)'
}
padding: "10px 20px",
borderRadius: "4px",
background: alpha(theme.palette.primary.main, 0.3),
color: "rgb(70,70,70)",
},
});
export class Alert extends Component {
render(){
return(
render() {
return (
<div className={this.props.classes.alert}>
<Typography>
{this.props.children}
</Typography>
<Typography>{this.props.children}</Typography>
</div>
);
}
}
export default withStyles(styles, { withTheme: true })(Alert);

View File

@ -12,6 +12,7 @@ import "./generator/index";
import { ZoomToFitControl } from "@blockly/zoom-to-fit";
import { initialXml } from "./initialXml.js";
import { getMaxInstances } from "./helpers/maxInstances";
import { Backpack } from "@blockly/workspace-backpack";
class BlocklyWindow extends Component {
constructor(props) {
@ -35,12 +36,23 @@ class BlocklyWindow extends Component {
Blockly.svgResize(workspace);
const zoomToFit = new ZoomToFitControl(workspace);
zoomToFit.init();
// Initialize plugin.
const backpack = new Backpack(workspace);
backpack.init();
}
componentDidUpdate(props) {
const workspace = Blockly.getMainWorkspace();
var xml = this.props.initialXml;
if (props.selectedBoard !== this.props.selectedBoard) {
// change board
if(!xml) xml = initialXml;
var xmlDom = Blockly.Xml.textToDom(xml);
Blockly.Xml.clearWorkspaceAndLoadFromXml(xmlDom, workspace);
// var toolbox = workspace.getToolbox();
// workspace.updateToolbox(toolbox.toolboxDef_);
}
// if svg is true, then the update process is done in the BlocklySvg component
if (props.initialXml !== xml && !this.props.svg) {
// guarantees that the current xml-code (this.props.initialXml) is rendered
@ -51,7 +63,7 @@ class BlocklyWindow extends Component {
if (props.language !== this.props.language) {
// change language
if (!xml) xml = initialXml;
var xmlDom = Blockly.Xml.textToDom(xml);
xmlDom = Blockly.Xml.textToDom(xml);
Blockly.Xml.clearWorkspaceAndLoadFromXml(xmlDom, workspace);
// var toolbox = workspace.getToolbox();
// workspace.updateToolbox(toolbox.toolboxDef_);
@ -124,14 +136,16 @@ BlocklyWindow.propTypes = {
onChangeWorkspace: PropTypes.func.isRequired,
clearStats: PropTypes.func.isRequired,
renderer: PropTypes.string.isRequired,
sounds: PropTypes.string.isRequired,
sounds: PropTypes.bool.isRequired,
language: PropTypes.string.isRequired,
selectedBoard: PropTypes.string.isRequired,
};
const mapStateToProps = (state) => ({
renderer: state.general.renderer,
sounds: state.general.sounds,
language: state.general.language,
selectedBoard: state.board.board,
});
export default connect(mapStateToProps, { onChangeWorkspace, clearStats })(

View File

@ -45,15 +45,11 @@ Blockly.Blocks['sensebox_rgb_led'] = {
Blockly.Blocks['sensebox_ws2818_led_init'] = {
init: function () {
var dropdownOptions = [[Blockly.Msg.senseBox_ultrasonic_port_A, '1'],
[Blockly.Msg.senseBox_ultrasonic_port_B, '3'], [Blockly.Msg.senseBox_ultrasonic_port_C, '5']];
this.setColour(getColour().sensebox);
this.appendDummyInput()
.appendField(Blockly.Msg.senseBox_ws2818_rgb_led_init)
.appendField("Port:")
.appendField(new Blockly.FieldDropdown(dropdownOptions), "Port")
.appendField(new Blockly.FieldDropdown(selectedBoard().digitalPinsRGB), "Port")
this.appendValueInput("BRIGHTNESS", "brightness")
.appendField((Blockly.Msg.senseBox_ws2818_rgb_led_brightness));
this.appendValueInput("NUMBER", "number")
@ -66,15 +62,11 @@ Blockly.Blocks['sensebox_ws2818_led_init'] = {
Blockly.Blocks['sensebox_ws2818_led'] = {
init: function () {
var dropdownOptions = [[Blockly.Msg.senseBox_ultrasonic_port_A, '1'],
[Blockly.Msg.senseBox_ultrasonic_port_B, '3'], [Blockly.Msg.senseBox_ultrasonic_port_C, '5']];
this.setColour(getColour().sensebox);
this.appendDummyInput()
.appendField(Blockly.Msg.senseBox_ws2818_rgb_led)
.appendField("Port:")
.appendField(new Blockly.FieldDropdown(dropdownOptions), "Port")
.appendField(new Blockly.FieldDropdown(selectedBoard().digitalPinsRGB), "Port")
this.appendValueInput("POSITION", "position")
.appendField((Blockly.Msg.senseBox_ws2818_rgb_led_position));
this.appendValueInput("COLOR", 'Number')

View File

@ -114,10 +114,8 @@ Blockly.Blocks["sensebox_sensor_sds011"] = {
)
.appendField(Blockly.Msg.senseBox_sds011_dimension)
.appendField(
new Blockly.FieldDropdown([
[Blockly.Msg.senseBox_sds011_serial1, "Serial1"],
[Blockly.Msg.senseBox_sds011_serial2, "Serial2"],
]),
new Blockly.FieldDropdown(
selectedBoard().serial),
"SERIAL"
);
this.setOutput(true, Types.DECIMAL.typeName);

View File

@ -148,53 +148,7 @@ Blockly["Arduino"].init = function (workspace) {
// Blockly.Names.DEVELOPER_VARIABLE_TYPE));
// }
const doubleVariables = workspace.getVariablesOfType("Number");
let i = 0;
let variableCode = "";
for (i = 0; i < doubleVariables.length; i += 1) {
variableCode +=
"double " +
Blockly["Arduino"].nameDB_.getName(
doubleVariables[i].getId(),
Blockly.Variables.NAME_TYPE
) +
" = 0; \n\n";
}
const stringVariables = workspace.getVariablesOfType("String");
for (i = 0; i < stringVariables.length; i += 1) {
variableCode +=
"String " +
Blockly["Arduino"].nameDB_.getName(
stringVariables[i].getId(),
Blockly.Variables.NAME_TYPE
) +
' = ""; \n\n';
}
const booleanVariables = workspace.getVariablesOfType("Boolean");
for (i = 0; i < booleanVariables.length; i += 1) {
variableCode +=
"boolean " +
Blockly["Arduino"].nameDB_.getDistinctName(
booleanVariables[i].getId(),
Blockly.Variables.NAME_TYPE
) +
" = false; \n\n";
}
const colourVariables = workspace.getVariablesOfType("Colour");
for (i = 0; i < colourVariables.length; i += 1) {
variableCode +=
"RGB " +
Blockly["Arduino"].nameDB_.getName(
colourVariables[i].getId(),
Blockly.Variables.NAME_TYPE
) +
" = {0, 0, 0}; \n\n";
}
Blockly["Arduino"].variablesInitCode_ = variableCode;
};
/**

View File

@ -101,7 +101,6 @@ Blockly.Arduino.sensebox_phyphox_graph = function () {
Blockly.Arduino.sensebox_phyphox_experiment_send = function () {
var branch = Blockly.Arduino.statementToCode(this, "sendValues");
var blocks = this.getDescendants();
console.log(blocks);
var count = 0;
if (blocks !== undefined) {
for (var i = 0; i < blocks.length; i++) {
@ -115,7 +114,6 @@ Blockly.Arduino.sensebox_phyphox_experiment_send = function () {
var string = "";
for (var j = 1; j <= count; j++) {
console.log("append");
if (string === "") {
string += `channel${j}`;
} else if (string !== "") {

View File

@ -203,6 +203,5 @@ Blockly.Arduino.sensebox_sd_save_for_osem = function (block) {
Blockly.Arduino.definitions_["SENSOR_ID" + id + ""] =
"const char SENSOR_ID" + id + '[] PROGMEM = "' + sensor_id + '";';
code += "addMeasurement(SENSOR_ID" + id + "," + sensor_value + ");\n";
console.log(code);
return code;
};

View File

@ -1,4 +1,12 @@
import Blockly from "blockly";
//import store from "../../../store";
// preperations for the esp board
// var selectedBoard = store.getState().board.board;
// store.subscribe(() => {
// selectedBoard = store.getState().board.board;
// });
/* Wifi connection and openSenseMap Blocks*/
Blockly.Arduino.sensebox_wifi = function (block) {
@ -110,3 +118,5 @@ Blockly.Arduino.sensebox_ethernetIp = function () {
var code = "Ethernet.localIP()";
return [code, Blockly.Arduino.ORDER_ATOMIC];
};

View File

@ -2,10 +2,17 @@ import Blockly from "blockly";
const setVariableFunction = function (defaultValue) {
return function (block) {
const variableName = Blockly["Arduino"].nameDB_.getName(
block.getFieldValue("VAR"),
Blockly.Variables.NAME_TYPE
);
var id = block.getFieldValue("VAR");
const variableName = Blockly.Variables.getVariable(
Blockly.getMainWorkspace(),
id
).name;
// const variableName = Blockly["Arduino"].nameDB_.getName(
// id,
// Blockly.Variables.NAME_TYPE
// );
const variableValue = Blockly["Arduino"].valueToCode(
block,
"VALUE",
@ -17,31 +24,10 @@ const setVariableFunction = function (defaultValue) {
.getAllVariables();
const myVar = allVars.filter((v) => v.name === variableName)[0];
var code = "";
switch (myVar.type) {
default:
Blockly.Arduino.variables_[variableName + myVar.type] =
myVar.type + " " + myVar.name + ";\n";
code = variableName + " = " + (variableValue || defaultValue) + ";\n";
break;
case "Array":
var arrayType;
var number;
if (this.getChildren().length > 0) {
if (this.getChildren()[0].type === "lists_create_empty") {
arrayType = this.getChildren()[0].getFieldValue("type");
number = Blockly.Arduino.valueToCode(
this.getChildren()[0],
"NUMBER",
Blockly["Arduino"].ORDER_ATOMIC
);
Blockly.Arduino.variables_[
myVar + myVar.type
] = `${arrayType} ${myVar.name} [${number}];\n`;
}
}
break;
if (myVar !== undefined) {
Blockly.Arduino.variables_[variableName + myVar.type] =
myVar.type + " " + myVar.name + ";\n";
code = variableName + " = " + (variableValue || defaultValue) + ";\n";
}
return code;
};

View File

@ -23,6 +23,11 @@ const sensebox_mcu = {
["C5", "5"],
["C6", "6"],
],
digitalPinsRGB: [
["A", "1"],
["B", "3"],
["C", "5"],
],
digitalPinsButton: [
["on Board", "0"],
["A1", "1"],
@ -126,6 +131,124 @@ const sensebox_mcu = {
parseKey: "_*_",
};
export const selectedBoard = () => {
return sensebox_mcu;
//senseBox MCU mini
const sensebox_mini = {
description: "senseBox Mini",
compilerFlag: "arduino:samd",
digitalPins: [
["IO1", "1"],
["IO2", "2"],
],
digitalPinsLED: [
["BUILTIN_1", "7"],
["BUILTIN_2", "8"],
["IO1", "1"],
["IO2", "2"],
],
digitalPinsRGB: [
["on Board", "6"],
["IO1", "1"],
["IO2", "2"],
],
digitalPinsButton: [
["on Board", "0"],
["IO1", "1"],
["IO2", "2"],
],
pwmPins: [
["IO1", "1"],
["IO2", "2"],
],
serial: [
["serial", "SerialUSB"],
["serial_1", "Serial1"],
],
serialPins: {
SerialUSB: [
["RX", ""],
["TX", ""],
],
Serial1: [
["RX", "11"],
["TX", "10"],
],
Serial2: [
["RX", "13"],
["TX", "12"],
],
},
serialSpeed: [
["300", "300"],
["600", "600"],
["1200", "1200"],
["2400", "2400"],
["4800", "4800"],
["9600", "9600"],
["14400", "14400"],
["19200", "19200"],
["28800", "28800"],
["31250", "31250"],
["38400", "38400"],
["57600", "57600"],
["115200", "115200"],
],
spi: [["SPI", "SPI"]],
spiPins: {
SPI: [
["MOSI", "19"],
["MISO", "21"],
["SCK", "20"],
],
},
spiClockDivide: [
["2 (8MHz)", "SPI_CLOCK_DIV2"],
["4 (4MHz)", "SPI_CLOCK_DIV4"],
["8 (2MHz)", "SPI_CLOCK_DIV8"],
["16 (1MHz)", "SPI_CLOCK_DIV16"],
["32 (500KHz)", "SPI_CLOCK_DIV32"],
["64 (250KHz)", "SPI_CLOCK_DIV64"],
["128 (125KHz)", "SPI_CLOCK_DIV128"],
],
i2c: [["I2C", "Wire"]],
i2cPins: {
Wire: [
["SDA", "17"],
["SCL", "16"],
],
},
i2cSpeed: [
["100kHz", "100000L"],
["400kHz", "400000L"],
],
builtinLed: [
["BUILTIN_1", "7"],
["BUILTIN_2", "8"],
],
interrupt: [
["interrupt1", "1"],
["interrupt2", "2"],
],
analogPins: [
["A1", "A1"],
["A2", "A2"],
],
serial_baud_rate: 9600,
parseKey: "_*_",
};
var board = sensebox_mcu
export const setBoard = (selectedBoard) => {
if (selectedBoard === "mini"){
board = sensebox_mini
}
else {
board = sensebox_mcu
}
}
export const selectedBoard = () => {
return board;
};

View File

@ -142,6 +142,7 @@ export const UI = {
button_cancel: "Abbrechen",
button_close: "Schließen",
button_save: "Speichern",
button_accept: "Bestätigen",
button_compile: "Kompilieren",
button_create_variableCreate: "Erstelle Variable",
@ -182,7 +183,8 @@ export const UI = {
settings_sounds: "Töne",
settings_sounds_text:
"Aktiviere oder Deaktiviere Töne beim hinzufügen und löschen von Blöcken. Standardmäßig deaktiviert",
settings_board: "Board",
settings_board_text: "Wähle dein verwendetes Board aus",
/**
* 404
*/
@ -229,6 +231,12 @@ export const UI = {
builder_requirements_head: "Voraussetzungen",
builder_requirements_order:
"Beachte, dass die Reihenfolge des Anhakens maßgebend ist.",
builder_difficulty: "Schwierigkeitsgrad",
builder_public_head: "Tutorial veröffentlichen",
builder_public_label: "Tutorial für alle Nutzer:innen veröffentlichen",
builder_review_head: "Tutorial veröffentlichen",
builder_review_text:
"Du kannst dein Tutorial direkt über den Link mit anderen Personen teilen. Wenn du dein Tutorial für alle Nutzer:innen in der Überischt veröffenltichen wollen kannst du es hier aktivieren. Ein Administrator wird dein Tutorial ansehen und anschließend freischalten.",
/**
* Login
@ -242,7 +250,7 @@ export const UI = {
/**
* Navbar
*/
navbar_blockly: "Blockly",
navbar_tutorials: "Tutorials",
navbar_tutorialbuilder: "Tutorial erstellen",
navbar_gallery: "Galerie",
@ -283,4 +291,25 @@ export const UI = {
drawer_ideerror_head: "Hoppla da ist was schief gegangen.",
drawer_ideerror_text:
"Beim kompilieren ist ein Fehler aufgetreten, überprüfe deine Blöcke.",
/**
* Code Editor
* */
codeeditor_libraries_head: "Installierte Arduino Libraries",
codeeditor_libraries_text:
"Für die Dokumentation sehen Sie sich die installierten Bibliotheken und deren Beispiele an",
codeeditor_save_code: "Code herunterladen",
codeeditor_open_code: "Code öffnen",
codeeditor_reset_code: "Code zurücksetzen",
codeeditor_blockly_code: "Lade Blockly Code",
codeeditor_compile_progress:
"Dein Code wird nun kompiliert und anschließend auf deinen Computer heruntergeladen",
/**
* Device Selction
* */
deviceselection_head: "Welches Board benutzt du?",
deviceselection_keep_selection: "Speichere meine Auswahl fürs nächste Mal (Du kannst das Board später in den Einstellungen wechseln)",
deviceselection_footnote: "Hier kommst du zur alten Blockly Version für den ",
deviceselection_footnote_02: "oder die"
};

View File

@ -177,6 +177,8 @@ export const UI = {
settings_sounds: "Sound",
settings_sounds_text:
"Enable or disable sounds when adding and deleting blocks. Disabled by default",
settings_board: "Board",
settings_board_text: "Choose your board",
/**
* 404
@ -223,6 +225,12 @@ export const UI = {
builder_requirements_head: "Requirements.",
builder_requirements_order:
"Note that the order of ticking is authoritative.",
builder_difficulty: "Difficulty level",
builder_public_head: "Publish tutorial",
builder_public_label: "Publish tutorial for all users",
builder_review_head: "Publish tutorial",
builder_review_text:
"You can share your tutorial with other people directly from the link. If you want to publish your tutorial for all users in the overview you can activate it here. An administrator will view your tutorial and then activate it.",
/**
* Login
@ -238,7 +246,7 @@ export const UI = {
/**
* Navbar
*/
navbar_blockly: "Blockly",
navbar_tutorials: "Tutorials",
navbar_tutorialbuilder: "Create tutorial",
navbar_gallery: "Gallery",
@ -278,4 +286,27 @@ export const UI = {
*/
drawer_ideerror_head: "Oops something went wrong",
drawer_ideerror_text: "An error occurred while compiling, check your blocks",
/**
* Code Editor
* */
codeeditor_libraries_head: "Installed Arduino Libraries",
codeeditor_libraries_text:
"For documentation, view the installed libraries and their examples",
codeeditor_save_code: "Download code",
codeeditor_open_code: "Open code",
codeeditor_reset_code: "Reset code",
codeeditor_blockly_code: "Load blockly code",
codeeditor_compile_progress:
"Your code will now be compiled and then downloaded to your computer",
/**
* Device Selction
* */
deviceselection_head: "Which board are you using?",
deviceselection_keep_selection: "Save my choice (You can change the board later in the settings)",
deviceselection_footnote: "Here you can access the old blockly Version for the",
deviceselection_footnote_02: "or the",
};

View File

@ -21,11 +21,8 @@ class Toolbox extends React.Component {
[`${Blockly.Msg.variable_LONG}`, "long"],
[`${Blockly.Msg.variable_DECIMAL}`, "float"],
[`${Blockly.Msg.variables_TEXT}`, "String"],
[`${Blockly.Msg.variables_ARRAY}`, "Array"],
[`${Blockly.Msg.variables_CHARACTER}`, "char"],
[`${Blockly.Msg.variables_BOOLEAN}`, "boolean"],
[`${Blockly.Msg.variables_NULL}`, "void"],
[`${Blockly.Msg.variables_UNDEF}`, "undefined"],
]
);
typedVarModal.init();

View File

@ -0,0 +1,279 @@
import React from "react";
import { useState, useRef } from "react";
import { default as MonacoEditor } from "@monaco-editor/react";
import { withRouter } from "react-router-dom";
import { Button, Grid } from "@material-ui/core";
import Blockly from "blockly/core";
import Divider from "@material-ui/core/Divider";
import { saveAs } from "file-saver";
import Drawer from "@material-ui/core/Drawer";
import Sidebar from "./Sidebar";
import Dialog from "../Dialog";
import SaveIcon from "./SaveIcon";
import store from "../../store";
const CodeEditor = (props) => {
//const [filehandle, setFileHandle] = useState();
const [fileContent, setFileContent] = useState("");
const [progress, setProgress] = useState(false);
// const [id, setId] = useState("");
const [open, setOpen] = useState(false);
const [error, setError] = useState("");
const editorRef = useRef(null);
const [autoSave, setAutoSave] = useState(false);
const [time, setTime] = useState(null);
const [value, setValue] = useState("");
const [resetDialog, setResetDialog] = useState(false);
const compile = () => {
setProgress(true);
const data = {
board: process.env.REACT_APP_BOARD,
sketch: editorRef.current.getValue(),
};
fetch(`${process.env.REACT_APP_COMPILER_URL}/compile`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
})
.then((response) => response.json())
.then((data) => {
if (data.code === "Internal Server Error") {
setProgress(false);
setOpen(true);
setError(data.message);
}
setProgress(false);
const result = data.data.id;
//setId(result);
const filename = "sketch";
window.open(
`${process.env.REACT_APP_COMPILER_URL}/download?id=${result}&board=${process.env.REACT_APP_BOARD}&filename=${filename}`,
"_self"
);
})
.catch((err) => {
console.log(err);
});
};
const saveIno = () => {
var filename = "sketch";
var code = editorRef.current.getValue();
filename = `${filename}.ino`;
var blob = new Blob([code], { type: "text/plain;charset=utf-8" });
saveAs(blob, filename);
};
const openIno = async () => {
const [myFileHandle] = await window.showOpenFilePicker();
//setFileHandle(myFileHandle);
const file = await myFileHandle.getFile();
const contents = await file.text();
setFileContent(contents);
};
const toggleDrawer = (anchor, open) => (event) => {
if (
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
setOpen(false);
};
const resetCode = () => {
const resetCode = `
#include <senseBoxIO.h> //needs to be always included
void setup () {
}
void loop() {
}`;
editorRef.current.setValue(resetCode);
};
const resetTimeout = (id, newID) => {
clearTimeout(id);
return newID;
};
const editValue = (value) => {
setTime(resetTimeout(time, setTimeout(saveValue, 400)));
setValue(value);
};
const saveValue = () => {
localStorage.setItem("ArduinoCode", value);
setAutoSave(true);
setTimeout(() => setAutoSave(false), 1000);
};
const getBlocklyCode = () => {
var code = store.getState().workspace.code.arduino;
editorRef.current.setValue(code);
};
return (
<div>
<Grid container spacing={2}>
<Drawer
anchor={"bottom"}
open={open}
onClose={toggleDrawer("bottom", false)}
>
<h2
style={{
color: "#4EAF47",
paddingLeft: "1rem",
paddingRight: "1rem",
}}
>
{Blockly.Msg.drawer_ideerror_head}
</h2>
<p
style={{
color: "#4EAF47",
paddingLeft: "1rem",
paddingRight: "1rem",
}}
>
{Blockly.Msg.drawer_ideerror_text}
</p>
<Divider style={{ backgroundColor: "white" }} />
<p
style={{
backgroundColor: "black",
color: "#E47128",
padding: "1rem",
}}
>
{" "}
{`${error}`}{" "}
</p>
</Drawer>
<Grid item lg={8} md={8}>
<div style={{ display: "flex", alignItems: "center" }}>
<h1>Code Editor</h1>
<SaveIcon loading={autoSave} />
</div>
<MonacoEditor
height="80vh"
onChange={(value) => {
editValue(value);
}}
defaultLanguage="cpp"
defaultValue={
localStorage.getItem("ArduinoCode")
? localStorage.getItem("ArduinoCode")
: `
#include <senseBoxIO.h> //needs to be always included
void setup () {
}
void loop() {
}`
}
value={fileContent}
onMount={(editor, monaco) => {
editorRef.current = editor;
}}
/>
</Grid>
<Grid item lg={4} md={4}>
<Button
style={{ padding: "1rem", margin: "1rem" }}
variant="contained"
color="primary"
onClick={() => compile()}
>
Kompilieren
</Button>
<Button
style={{ padding: "1rem", margin: "1rem" }}
variant="contained"
color="primary"
onClick={() => saveIno()}
>
{Blockly.Msg.codeeditor_save_code}
</Button>
<Button
style={{ padding: "1rem", margin: "1rem" }}
variant="contained"
color="primary"
onClick={() => openIno()}
>
{Blockly.Msg.codeeditor_open_code}
</Button>
<Button
style={{ padding: "1rem", margin: "1rem" }}
variant="contained"
color="primary"
onClick={() => setResetDialog(true)}
>
{Blockly.Msg.codeeditor_reset_code}
</Button>
<Button
style={{ padding: "1rem", margin: "1rem" }}
variant="contained"
color="primary"
onClick={() => getBlocklyCode()}
>
{Blockly.Msg.codeeditor_blockly_code}
</Button>
<Sidebar />
<Dialog
style={{ zIndex: 9999999 }}
fullWidth
maxWidth={"sm"}
open={progress}
title={"Code wird kompiliert"}
content={""}
>
<div>{Blockly.Msg.codeeditor_compile_progress}</div>
</Dialog>{" "}
<Dialog
open={resetDialog}
title={Blockly.Msg.resetDialog_headline}
content={Blockly.Msg.resetDialog_text}
onClose={() => {
setResetDialog(false);
}}
onClick={() => {
setResetDialog(false);
}}
button={Blockly.Msg.button_cancel}
>
{" "}
<div style={{ marginTop: "10px" }}>
<Button
variant="contained"
color="primary"
onClick={() => {
resetCode();
setResetDialog(false);
}}
>
Zurücksetzen
</Button>
</div>
</Dialog>
</Grid>
</Grid>
</div>
);
};
export default withRouter(CodeEditor);

View File

@ -0,0 +1,331 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { workspaceName } from "../../actions/workspaceActions";
import { detectWhitespacesAndReturnReadableResult } from "../../helpers/whitespace";
import { withStyles } from "@material-ui/core/styles";
import Button from "@material-ui/core/Button";
import Backdrop from "@material-ui/core/Backdrop";
import CircularProgress from "@material-ui/core/CircularProgress";
import IconButton from "@material-ui/core/IconButton";
import Tooltip from "@material-ui/core/Tooltip";
import Divider from "@material-ui/core/Divider";
import { faClipboardCheck } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as Blockly from "blockly/core";
import Copy from "../copy.svg";
import MuiDrawer from "@material-ui/core/Drawer";
import Dialog from "../Dialog";
const styles = (theme) => ({
backdrop: {
zIndex: theme.zIndex.drawer + 1,
color: "#fff",
},
iconButton: {
backgroundColor: theme.palette.button.compile,
color: theme.palette.primary.contrastText,
width: "40px",
height: "40px",
"&:hover": {
backgroundColor: theme.palette.button.compile,
color: theme.palette.primary.contrastText,
},
},
button: {
backgroundColor: theme.palette.button.compile,
color: theme.palette.primary.contrastText,
"&:hover": {
backgroundColor: theme.palette.button.compile,
color: theme.palette.primary.contrastText,
},
},
});
const Drawer = withStyles((theme) => ({
paperAnchorBottom: {
backgroundColor: "black",
height: "20vH",
},
}))(MuiDrawer);
class Compile extends Component {
constructor(props) {
super(props);
this.state = {
progress: false,
open: false,
file: false,
title: "",
content: "",
name: props.name,
error: "",
appLink: "",
appDialog: false,
};
}
componentDidMount() {}
componentDidUpdate(props) {
if (props.name !== this.props.name) {
this.setState({ name: this.props.name });
}
}
compile = () => {
this.setState({ progress: true });
const data = {
board: process.env.REACT_APP_BOARD,
sketch: this.props.arduino,
};
fetch(`${process.env.REACT_APP_COMPILER_URL}/compile`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
})
.then((response) => response.json())
.then((data) => {
if (data.code === "Internal Server Error") {
this.setState({
progress: false,
file: false,
open: true,
title: Blockly.Msg.compiledialog_headline,
content: Blockly.Msg.compiledialog_text,
error: data.message,
});
}
this.setState({ id: data.data.id }, () => {
this.createFileName();
});
})
.catch((err) => {
console.log(err);
//this.setState({ progress: false, file: false, open: true, title: Blockly.Msg.compiledialog_headline, content: Blockly.Msg.compiledialog_text });
});
};
download = () => {
const id = this.state.id;
const filename = detectWhitespacesAndReturnReadableResult(this.state.name);
this.toggleDialog();
this.props.workspaceName(this.state.name);
window.open(
`${process.env.REACT_APP_COMPILER_URL}/download?id=${id}&board=${process.env.REACT_APP_BOARD}&filename=${filename}`,
"_self"
);
this.setState({ progress: false });
};
toggleDialog = () => {
this.setState({ open: !this.state, progress: false, appDialog: false });
};
createFileName = () => {
if (this.props.platform === true) {
const filename = detectWhitespacesAndReturnReadableResult(
this.state.name
);
this.setState({
link: `blocklyconnect-app://sketch/${filename}/${this.state.id}`,
});
this.setState({ appDialog: true });
} else {
if (this.state.name) {
this.download();
} else {
this.setState({
file: true,
open: true,
title: "Projekt kompilieren",
content:
"Bitte gib einen Namen für die Bennenung des zu kompilierenden Programms ein und bestätige diesen mit einem Klick auf 'Eingabe'.",
});
}
}
// if (this.state.name) {
// this.download();
// } else {
// this.setState({
// file: true,
// open: true,
// title: "Projekt 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 });
};
toggleDrawer = (anchor, open) => (event) => {
if (
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
this.setState({ open: false });
};
render() {
return (
<div style={{}}>
{this.props.iconButton ? (
<Tooltip
title={Blockly.Msg.tooltip_compile_code}
arrow
style={{ marginRight: "5px" }}
>
<IconButton
className={`compileBlocks ${this.props.classes.iconButton}`}
onClick={() => this.compile()}
>
<FontAwesomeIcon icon={faClipboardCheck} size="xs" />
</IconButton>
</Tooltip>
) : (
<Button
style={{ float: "right", color: "white" }}
variant="contained"
className={this.props.classes.button}
onClick={() => this.compile()}
>
<FontAwesomeIcon
icon={faClipboardCheck}
style={{ marginRight: "5px" }}
/>{" "}
Kompilieren
</Button>
)}
{this.props.platform === false ? (
<Backdrop
className={this.props.classes.backdrop}
open={this.state.progress}
>
<div className="overlay">
<img src={Copy} width="400" alt="copyimage"></img>
<h2>{Blockly.Msg.compile_overlay_head}</h2>
<p>{Blockly.Msg.compile_overlay_text}</p>
<p>
{Blockly.Msg.compile_overlay_help}
<a href="/faq" target="_blank">
FAQ
</a>
</p>
<CircularProgress color="inherit" />
</div>
</Backdrop>
) : (
<Backdrop
className={this.props.classes.backdrop}
open={this.state.progress}
>
<div className="overlay">
{/* <img src={Copy} width="400" alt="copyimage"></img> */}
<h2>Dein Code wird kompiliert!</h2>
<p>übertrage ihn anschließend mithlfe der senseBoxConnect-App</p>
<p>
{Blockly.Msg.compile_overlay_help}
<a href="/faq" target="_blank">
FAQ
</a>
</p>
<CircularProgress color="inherit" />
</div>
</Backdrop>
)}
<Drawer
anchor={"bottom"}
open={this.state.open}
onClose={this.toggleDrawer("bottom", false)}
>
<h2
style={{
color: "#4EAF47",
paddingLeft: "1rem",
paddingRight: "1rem",
}}
>
{Blockly.Msg.drawer_ideerror_head}
</h2>
<p
style={{
color: "#4EAF47",
paddingLeft: "1rem",
paddingRight: "1rem",
}}
>
{Blockly.Msg.drawer_ideerror_text}
</p>
<Divider style={{ backgroundColor: "white" }} />
<p
style={{
backgroundColor: "black",
color: "#E47128",
padding: "1rem",
}}
>
{" "}
{`${this.state.error}`}{" "}
</p>
</Drawer>
<Dialog
style={{ zIndex: 9999999 }}
fullWidth
maxWidth={"sm"}
open={this.state.appDialog}
title=""
content={""}
onClose={this.toggleDialog}
onClick={this.toggleDialog}
button={Blockly.Msg.button_close}
>
<div>
<p>Dein Code wurde erfolgreich kompiliert</p>
<a href={this.state.link}>
<Button
style={{ color: "white" }}
variant="contained"
className={this.props.classes.button}
onClick={() => this.toggleDialog()}
>
<FontAwesomeIcon
icon={faClipboardCheck}
style={{ marginRight: "5px" }}
/>{" "}
Starte Übertragung
</Button>
</a>
</div>
</Dialog>
</div>
);
}
}
Compile.propTypes = {
arduino: PropTypes.string.isRequired,
name: PropTypes.string,
workspaceName: PropTypes.func.isRequired,
platform: PropTypes.bool.isRequired,
};
const mapStateToProps = (state) => ({
arduino: state.workspace.code.arduino,
name: state.workspace.name,
platform: state.general.platform,
});
export default connect(mapStateToProps, { workspaceName })(
withStyles(styles, { withTheme: true })(Compile)
);

View File

@ -0,0 +1,40 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCircleNotch, faSave } from "@fortawesome/free-solid-svg-icons";
import Tooltip from "@material-ui/core/Tooltip";
import React from "react";
const SaveIcon = ({ loading }) => (
<Tooltip title={"Auto save enabled"} arrow placement="right">
<div
style={{
position: "relative",
width: "2rem",
height: "2rem",
margin: "1rem",
}}
>
{loading && (
<FontAwesomeIcon
style={{ position: "absolute" }}
icon={faCircleNotch}
spin={true}
size="2x"
color="grey"
/>
)}
<FontAwesomeIcon
style={{
position: "absolute",
left: "50%",
top: "50%",
transform: "translate(-50%,-50%)",
}}
icon={faSave}
color={loading ? "grey" : "green"}
size={loading ? "1x" : "lg"}
/>
</div>
</Tooltip>
);
export default SaveIcon;

View File

@ -0,0 +1,91 @@
import { useState } from "react";
import Button from "@material-ui/core/Button";
const SerialMonitor = () => {
const [serialPortContent, setSerialPortContent] = useState([]);
const [checked, setChecked] = useState(false);
const handleClick = () => setChecked(!checked);
const connectPort = async () => {
try {
const port = await navigator.serial.requestPort();
await port.open({ baudRate: 9600 });
while (port.readable) {
const reader = port.readable.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) {
// Allow the serial port to be closed later.
reader.releaseLock();
break;
}
if (value) {
// byte array to string: https://stackoverflow.com/a/37542820
const text = String.fromCharCode.apply(null, value);
setSerialPortContent((prevContent) => [
...prevContent,
[new Date(), text],
]);
}
}
} catch (error) {
setSerialPortContent((prevContent) => [
...prevContent,
[new Date(), error],
]);
}
}
} catch (error) {
setSerialPortContent((prevContent) => [
...prevContent,
[new Date(), error],
]);
}
};
return (
<>
<div className="flex items-center">
<Button type="button" variant="outlined" onClick={() => connectPort()}>
Connect to senseBox
</Button>
<label className="m-4 text-gray-700 text-base font-semibold px-6 py-3 rounded-lg">
Show timestamps
<input
onChange={handleClick}
checked={checked}
type="checkbox"
className="mx-4"
/>
</label>
<Button
type="button"
variant="outlined"
onClick={() => setSerialPortContent([])}
>
Clear
</Button>
</div>
<div className="font-mono">
{serialPortContent.map((log) => {
return (
<p>
{checked && (
<span className="font-medium mr-4">{log[0].toISOString()}</span>
)}
<span>{log[1]}</span>
</p>
);
})}
</div>
</>
);
};
export default SerialMonitor;

View File

@ -0,0 +1,140 @@
import React from "react";
import Blockly from "blockly";
import { useSelector } from "react-redux";
import Accordion from "@material-ui/core/Accordion";
import AccordionSummary from "@material-ui/core/AccordionSummary";
import AccordionDetails from "@material-ui/core/AccordionDetails";
import Typography from "@material-ui/core/Typography";
import { LibraryVersions } from "../../data/versions.js";
import { useMonaco } from "@monaco-editor/react";
import { Button } from "@material-ui/core";
import SerialMonitor from "./SerialMonitor.js";
import axios from "axios";
const Sidebar = () => {
//const [examples, setExamples] = React.useState([]);
const user = useSelector((state) => state.auth.user);
// useEffect(() => {
// axios
// .get("https://coelho.opensensemap.org/items/blocklysamples")
// .then((res) => {
// setExamples(res.data.data);
// });
// }, []);
const monaco = useMonaco();
const loadCode = (code) => {
monaco.editor.getModels()[0].setValue(code);
};
const getOsemScript = (id) => {
axios
.get(`https://api.opensensemap.org/boxes/${id}/script/`)
.then((res) => {
loadCode(res.data);
});
};
return (
<div>
{"serial" in navigator ? (
<Accordion>
<AccordionSummary
expandIcon={""}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography>Serial Monitor</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
<SerialMonitor />
</Typography>
</AccordionDetails>
</Accordion>
) : null}
{/* <Accordion>
<AccordionSummary
expandIcon={""}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography>Beispiele</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
{examples.map((object, i) => {
return (
<Button
style={{ padding: "1rem", margin: "1rem" }}
variant="contained"
color="primary"
key={i}
onClick={() => loadCode(object.code)}
>
{object.name}
</Button>
);
})}
</Typography>
</AccordionDetails>
</Accordion> */}
{user ? (
<Accordion>
<AccordionSummary
expandIcon={""}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography>Deine openSenseMap Codes</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
{user.boxes.map((box, i) => {
return (
<Button
style={{ padding: "1rem", margin: "1rem" }}
variant="contained"
color="primary"
key={i}
onClick={() => getOsemScript(box._id)}
>
{box.name}
</Button>
);
})}
</Typography>
</AccordionDetails>
</Accordion>
) : null}
<Accordion>
<AccordionSummary
expandIcon={""}
aria-controls="panel2a-content"
id="panel2a-header"
>
<Typography>{Blockly.Msg.codeeditor_libraries_head}</Typography>
</AccordionSummary>
<AccordionDetails
style={{ padding: 0, height: "60vH", backgroundColor: "white" }}
>
<Typography
style={{ overflow: "auto", width: "100%", padding: "1rem" }}
>
<p>{Blockly.Msg.codeeditor_libraries_text}</p>
{LibraryVersions().map((object, i) => {
return (
<p>
<a href={object.link} target="_blank" rel="noreferrer">
{object.library} {object.version}
</a>
</p>
);
})}
</Typography>
</AccordionDetails>
</Accordion>
</div>
);
};
export default Sidebar;

View File

@ -1,30 +1,25 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Prism from "prismjs";
import "prismjs/themes/prism.css";
import "prismjs/plugins/line-numbers/prism-line-numbers";
import "prismjs/plugins/line-numbers/prism-line-numbers.css";
import withWidth from '@material-ui/core/withWidth';
import { withStyles } from '@material-ui/core/styles';
import MuiAccordion from '@material-ui/core/Accordion';
import MuiAccordionSummary from '@material-ui/core/AccordionSummary';
import MuiAccordionDetails from '@material-ui/core/AccordionDetails';
import { Card } from '@material-ui/core';
import * as Blockly from 'blockly'
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import withWidth from "@material-ui/core/withWidth";
import { withStyles } from "@material-ui/core/styles";
import MuiAccordion from "@material-ui/core/Accordion";
import MuiAccordionSummary from "@material-ui/core/AccordionSummary";
import MuiAccordionDetails from "@material-ui/core/AccordionDetails";
import { Card } from "@material-ui/core";
import * as Blockly from "blockly";
import { default as MonacoEditor } from "@monaco-editor/react";
const Accordion = withStyles((theme) => ({
root: {
border: `1px solid ${theme.palette.secondary.main}`,
boxShadow: 'none',
'&:before': {
display: 'none',
boxShadow: "none",
"&:before": {
display: "none",
},
'&$expanded': {
margin: 'auto',
"&$expanded": {
margin: "auto",
},
},
expanded: {},
@ -34,15 +29,15 @@ const AccordionSummary = withStyles((theme) => ({
root: {
backgroundColor: theme.palette.secondary.main,
borderBottom: `1px solid white`,
marginBottom: '-1px',
minHeight: '50px',
'&$expanded': {
minHeight: '50px',
marginBottom: "-1px",
minHeight: "50px",
"&$expanded": {
minHeight: "50px",
},
},
content: {
'&$expanded': {
margin: '12px 0',
"&$expanded": {
margin: "12px 0",
},
},
expanded: {},
@ -54,40 +49,60 @@ const AccordionDetails = withStyles((theme) => ({
},
}))(MuiAccordionDetails);
class CodeViewer extends Component {
constructor(props) {
super(props);
this.state = {
code: this.props.arduino,
changed: false,
expanded: true,
componentHeight: null
componentHeight: null,
};
this.myDiv = React.createRef();
}
componentDidMount() {
Prism.highlightAll();
this.setState({ componentHeight: this.myDiv.current.offsetHeight + 'px' });
this.setState({ componentHeight: this.myDiv.current.offsetHeight + "px" });
}
componentDidUpdate(props, state) {
if (this.myDiv.current && this.myDiv.current.offsetHeight + 'px' !== this.state.componentHeight) {
this.setState({ componentHeight: this.myDiv.current.offsetHeight + 'px' });
componentDidUpdate(prevProps, prevState) {
// if (this.props.arduino !== prevProps.arduino) {
// this.setState({ changed: true });
// console.log(`code changed: ${this.state.changed}`);
// if (this.state.changed && prevState.code !== this.props.arduino) {
// this.setState({ code: this.props.arduino });
// this.setState({ changed: false });
// }
// if (this.state.code !== prevState.code && this.state.changed) {
// this.setState({ changed: false });
// }
// if (this.props.arduino !== this.state.code) {
// this.setState({ changed: true });
// //this.setState({ code: this.props.arduino });
// }
if (
this.myDiv.current &&
this.myDiv.current.offsetHeight + "px" !== this.state.componentHeight
) {
this.setState({
componentHeight: this.myDiv.current.offsetHeight + "px",
});
}
Prism.highlightAll();
}
onChange = () => {
this.setState({ expanded: !this.state.expanded });
}
};
render() {
var curlyBrackets = '{ }';
var unequal = '<>';
var curlyBrackets = "{ }";
var unequal = "<>";
return (
<Card style={{ height: '100%', maxHeight: '60vH' }} ref={this.myDiv}>
<Card style={{ height: "100%", maxHeight: "60vH" }} ref={this.myDiv}>
<Accordion
square={true}
style={{ margin: 0 }}
@ -95,15 +110,32 @@ class CodeViewer extends Component {
onChange={this.onChange}
>
<AccordionSummary>
<b style={{ fontSize: '20px', marginRight: '5px', width: '35px' }}>{curlyBrackets}</b>
<div style={{ margin: 'auto 5px 2px 0px' }}>{Blockly.Msg.codeviewer_arduino}</div>
<b style={{ fontSize: "20px", marginRight: "5px", width: "35px" }}>
{curlyBrackets}
</b>
<div style={{ margin: "auto 5px 2px 0px" }}>
{Blockly.Msg.codeviewer_arduino}
</div>
</AccordionSummary>
<AccordionDetails style={{ padding: 0, height: `calc(${this.state.componentHeight} - 50px - 50px)`, backgroundColor: 'white' }}>
<pre className="line-numbers" style={{ paddingBottom: 0, width: '100%', overflow: 'auto', scrollbarWidth: 'thin', height: 'calc(100% - 30px)', margin: '15px 0', paddingTop: 0, whiteSpace: 'pre-wrap', backgroundColor: 'white' }}>
<code className="language-clike">
{this.props.arduino}
</code>
</pre>
<AccordionDetails
style={{
padding: 0,
height: `calc(${this.state.componentHeight} - 50px - 50px)`,
backgroundColor: "white",
}}
>
<MonacoEditor
height="80vh"
defaultLanguage="cpp"
value={this.props.arduino}
// modified={this.props.arduino}
// original={this.state.code}
options={{
readOnly: true,
fontSize: "16px",
}}
/>
</AccordionDetails>
</Accordion>
<Accordion
@ -113,32 +145,43 @@ class CodeViewer extends Component {
onChange={this.onChange}
>
<AccordionSummary>
<b style={{ fontSize: '20px', marginRight: '5px', width: '35px' }}>{unequal}</b>
<div style={{ margin: 'auto 5px 2px 0px' }}>{Blockly.Msg.codeviewer_xml}</div>
<b style={{ fontSize: "20px", marginRight: "5px", width: "35px" }}>
{unequal}
</b>
<div style={{ margin: "auto 5px 2px 0px" }}>
{Blockly.Msg.codeviewer_xml}
</div>
</AccordionSummary>
<AccordionDetails style={{ padding: 0, height: `calc(${this.state.componentHeight} - 50px - 50px)`, backgroundColor: 'white' }}>
<pre className="line-numbers" style={{ paddingBottom: 0, width: '100%', overflow: 'auto', scrollbarWidth: 'thin', height: 'calc(100% - 30px)', margin: '15px 0', paddingTop: 0, whiteSpace: 'pre-wrap', backgroundColor: 'white' }}>
<code className="language-xml">
{`${this.props.xml}`}
</code>
</pre>
<AccordionDetails
style={{
padding: 0,
height: `calc(${this.state.componentHeight} - 50px - 50px)`,
backgroundColor: "white",
}}
>
<MonacoEditor
height="80vh"
defaultLanguage="xml"
value={this.props.xml}
readOnly={true}
/>
</AccordionDetails>
</Accordion>
</Card>
);
};
}
}
CodeViewer.propTypes = {
arduino: PropTypes.string.isRequired,
xml: PropTypes.string.isRequired,
tooltip: PropTypes.string.isRequired
tooltip: PropTypes.string.isRequired,
};
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
arduino: state.workspace.code.arduino,
xml: state.workspace.code.xml,
tooltip: state.workspace.code.tooltip
tooltip: state.workspace.code.tooltip,
});
export default connect(mapStateToProps, null)(withWidth()(CodeViewer));

View File

@ -10,6 +10,7 @@ import Navbar from './Navbar';
import Footer from './Footer';
import Routes from './Route/Routes';
import Cookies from './Cookies';
import { setBoard } from './Blockly/helpers/board';
class Content extends Component {
@ -19,6 +20,7 @@ class Content extends Component {
} else if (this.props.language === 'en_US') {
Blockly.setLocale(En);
}
setBoard(this.props.board)
}
componentDidUpdate(props) {
@ -29,6 +31,7 @@ class Content extends Component {
Blockly.setLocale(En);
}
}
setBoard(this.props.board)
}
render() {
@ -48,7 +51,8 @@ Content.propTypes = {
};
const mapStateToProps = state => ({
language: state.general.language
language: state.general.language,
board: state.board.board
});
export default connect(mapStateToProps, null)(Content);

View File

@ -9,7 +9,7 @@ class Cookies extends Component {
return (
<CookieConsent
location="bottom"
buttonText="Okay!!"
buttonText="Okay!"
cookieName="cookieConsent"
style={{ background: "#2B373B" }}
buttonStyle={{ background: "white", color: "#4EAF47", fontSize: "1rem" }}

View File

@ -0,0 +1,162 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Dialog from "./Dialog";
import { withStyles } from "@material-ui/core/styles";
import Checkbox from "@material-ui/core/Checkbox";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import * as Blockly from "blockly";
import { IconButton, Grid, Avatar, Typography } from "@material-ui/core";
import { setBoard } from "../actions/boardAction";
const styles = (theme) => ({
link: {
color: theme.palette.primary.main,
textDecoration: "none",
"&:hover": {
color: theme.palette.primary.main,
textDecoration: `underline`,
},
},
label: {
fontSize: "0.9rem",
color: "grey",
},
});
class DeviceSeclection extends Component {
constructor(props) {
var previousPageWasAnotherDomain = props.pageVisits === 0;
var userWantToKeepBoard = window.localStorage.getItem("board")
? true
: false;
super(props);
this.state = {
open: userWantToKeepBoard
? !userWantToKeepBoard
: previousPageWasAnotherDomain,
selectedBoard : "",
saveSettings: false,
};
}
toggleDialog = () => {
this.setState({ open: !this.state });
if(this.state.saveSettings){
window.localStorage.setItem("board", this.state.selectedBoard)
}
this.props.setBoard(this.state.selectedBoard)
};
onChange = (e) => {
if (e.target.checked) {
this.setState({ saveSettings: true});
} else {
this.setState({ saveSettings: false});
}
};
onclick = (e, value) => {
console.log(e, value)
this.setState({selectedBoard: value})
};
render() {
return (
<Dialog
style={{ zIndex: 9999999 }}
fullWidth
maxWidth={"xl"}
open={this.state.open}
title={Blockly.Msg.deviceselection_head}
content={""}
onClick={this.toggleDialog}
button={Blockly.Msg.button_accept}
disabled={this.state.selectedBoard===""}
>
<div>
<Grid container spacing={2} style={{ textAlign : "center" }}>
<Grid item xs={6}>
<IconButton onClick={(e) => this.onclick(e, "mcu")}>
<Avatar
alt="Sensebox MCU"
src="/media/hardware/senseboxmcu.png"
style={{
border: this.state.selectedBoard === "mcu" ? 'medium solid DeepSkyBlue': "0.1px solid lightgray",
width:"20vw",
height: "20vw"
}}
/>
</IconButton>
<p>senseBox MCU</p>
</Grid>
{/* <Grid item xs={4}>
<IconButton onClick={(e) => this.onclick(e, "esp")}>
<Avatar
alt="Sensebox ESP"
src="/media/hardware/senseboxmcu.png"
style={{
border: this.state.selectedBoard == "esp" ? 'medium solid DeepSkyBlue': "0.1px solid lightgray",
width:"20vw",
height: "20vw"
}}
/>
</IconButton>
<p>Sensebox ESP</p>
</Grid> */}
<Grid item xs={6}>
<IconButton onClick={(e) => this.onclick(e, "mini")}>
<Avatar
alt="Sensebox Mini"
src="/media/hardware/senseboxmcumini.png"
style={{
border: this.state.selectedBoard === "mini" ? 'medium solid DeepSkyBlue': "0.1px solid lightgray",
width:"20vw",
height: "20vw"
}}
/>
</IconButton>
<p>senseBox MCU:mini</p>
</Grid>
</Grid>
</div>
<FormControlLabel
style={{ marginTop: "20px" }}
classes={{ label: this.props.classes.label }}
control={
<Checkbox
size={"small"}
value={true}
checked={this.state.checked}
onChange={(e) => this.onChange(e)}
name="dialog"
color="primary"
/>
}
label={Blockly.Msg.deviceselection_keep_selection}
/>
<Typography variant="body1" >
{Blockly.Msg.deviceselection_footnote} <a href="https://sensebox.github.io/blockly/">Arduino UNO</a> {Blockly.Msg.deviceselection_footnote_02} <a href="/">senseBox MCU</a>
</Typography>
</Dialog>
);
}
}
DeviceSeclection.propTypes = {
pageVisits: PropTypes.number.isRequired,
};
const mapStateToProps = (state) => ({
pageVisits: state.general.pageVisits,
selectedBoard: state.board.board
});
export default connect(
mapStateToProps,
{setBoard}
)(withStyles(styles, { withTheme: true })(DeviceSeclection));

View File

@ -24,7 +24,7 @@ class Dialog extends Component {
</DialogContent>
<DialogActions>
{this.props.actions ? this.props.actions :
<Button onClick={this.props.onClick} color="primary">
<Button onClick={this.props.onClick} disabled={this.props.disabled} color="primary">
{this.props.button}
</Button>
}

View File

@ -1,179 +1,100 @@
import React, { Component } from 'react';
import React, { Component } from "react";
import Breadcrumbs from './Breadcrumbs';
import Breadcrumbs from "./Breadcrumbs";
import { withRouter } from 'react-router-dom';
import { withRouter } from "react-router-dom";
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import * as Blockly from 'blockly'
import ReactMarkdown from 'react-markdown';
import Container from '@material-ui/core/Container';
import ExpansionPanel from '@material-ui/core/ExpansionPanel';
import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary';
import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails';
import Button from "@material-ui/core/Button";
import Typography from "@material-ui/core/Typography";
import * as Blockly from "blockly";
import ReactMarkdown from "react-markdown";
import Container from "@material-ui/core/Container";
import ExpansionPanel from "@material-ui/core/ExpansionPanel";
import ExpansionPanelSummary from "@material-ui/core/ExpansionPanelSummary";
import ExpansionPanelDetails from "@material-ui/core/ExpansionPanelDetails";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChevronDown } from "@fortawesome/free-solid-svg-icons";
import { FaqQuestions } from '../data/faq'
import { FaqQuestions } from "../data/faq";
class Faq extends Component {
state = {
panel: "",
expanded: false,
};
state = {
panel: '',
expanded: false
}
handleChange = (panel) => {
this.setState({ panel: this.state.panel === panel ? "" : panel });
};
componentDidMount() {
// Ensure that Blockly.setLocale is adopted in the component.
// Otherwise, the text will not be displayed until the next update of the component.
handleChange = (panel) => {
this.setState({ panel: this.state.panel === panel ? '' : panel });
};
window.scrollTo(0, 0);
this.forceUpdate();
}
componentDidMount() {
// Ensure that Blockly.setLocale is adopted in the component.
// Otherwise, the text will not be displayed until the next update of the component.
window.scrollTo(0, 0)
this.forceUpdate();
}
render() {
const { panel } = this.state;
return (
<div>
<Breadcrumbs content={[{ link: this.props.location.pathname, title: 'FAQ' }]} />
<Container fixed>
<div style={{ margin: '0px 24px 0px 24px' }}>
<h1>FAQ</h1>
{FaqQuestions().map((object, i) => {
return (
<ExpansionPanel expanded={panel === `panel${i}`} onChange={() => this.handleChange(`panel${i}`)}>
<ExpansionPanelSummary
expandIcon={
<FontAwesomeIcon icon={faChevronDown} />
}
>
<Typography variant="h6">{object.question}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Typography>
<ReactMarkdown className="news" allowDangerousHtml="true" children={object.answer}>
</ReactMarkdown>
</Typography>
</ExpansionPanelDetails>
</ExpansionPanel>
)
})}
{
this.props.button ?
<Button
style={{ marginTop: '20px' }}
variant="contained"
color="primary"
onClick={() => { this.props.history.push(this.props.button.link) }}
>
{this.props.button.title}
</Button>
:
<Button
style={{ marginTop: '20px' }}
variant="contained"
color="primary"
onClick={() => { this.props.history.push('/') }}
>
{Blockly.Msg.button_back}
</Button>
}
</div>
</Container>
</div >
);
};
render() {
const { panel } = this.state;
return (
<div>
<Breadcrumbs
content={[{ link: this.props.location.pathname, title: "FAQ" }]}
/>
<Container fixed>
<div style={{ margin: "0px 24px 0px 24px" }}>
<h1>FAQ</h1>
{FaqQuestions().map((object, i) => {
return (
<ExpansionPanel
expanded={panel === `panel${i}`}
onChange={() => this.handleChange(`panel${i}`)}
>
<ExpansionPanelSummary
expandIcon={<FontAwesomeIcon icon={faChevronDown} />}
>
<Typography variant="h6">{object.question}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Typography>
<ReactMarkdown
className="news"
allowDangerousHtml="true"
children={object.answer}
></ReactMarkdown>
</Typography>
</ExpansionPanelDetails>
</ExpansionPanel>
);
})}
{this.props.button ? (
<Button
style={{ marginTop: "20px" }}
variant="contained"
color="primary"
onClick={() => {
this.props.history.push(this.props.button.link);
}}
>
{this.props.button.title}
</Button>
) : (
<Button
style={{ marginTop: "20px" }}
variant="contained"
color="primary"
onClick={() => {
this.props.history.push("/");
}}
>
{Blockly.Msg.button_back}
</Button>
)}
</div>
</Container>
</div>
);
}
}
export default withRouter(Faq);
/*
<ExpansionPanel expanded={panel === 'panel1'} onChange={() => this.handleChange('panel1')}>
<ExpansionPanelSummary
expandIcon={
<FontAwesomeIcon icon={faChevronDown} />
}
>
<Typography variant="h6">{Blockly.Msg.faq_q1_question}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Typography>
<ReactMarkdown className="news" allowDangerousHtml="true" children={Blockly.Msg.faq_q1_answer}>
</ReactMarkdown>
</Typography>
</ExpansionPanelDetails>
</ExpansionPanel>
<ExpansionPanel expanded={panel === 'panel2'} onChange={() => this.handleChange('panel2')}>
<ExpansionPanelSummary
expandIcon={
<FontAwesomeIcon icon={faChevronDown} />
}
>
<Typography>Frage 2</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Typography>
Donec placerat, lectus sed mattis semper, neque lectus feugiat lectus, varius pulvinar
diam eros in elit. Pellentesque convallis laoreet laoreet.
</Typography>
</ExpansionPanelDetails>
</ExpansionPanel>
<ExpansionPanel expanded={panel === 'panel3'} onChange={() => this.handleChange('panel3')}>
<ExpansionPanelSummary
expandIcon={
<FontAwesomeIcon icon={faChevronDown} />
}
>
<Typography>Frage 3</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Typography>
Nunc vitae orci ultricies, auctor nunc in, volutpat nisl. Integer sit amet egestas eros,
vitae egestas augue. Duis vel est augue.
</Typography>
</ExpansionPanelDetails>
</ExpansionPanel>
<ExpansionPanel expanded={panel === 'panel4'} onChange={() => this.handleChange('panel4')}>
<ExpansionPanelSummary
expandIcon={
<FontAwesomeIcon icon={faChevronDown} />
}
>
<Typography>Frage 4</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<Typography>
Nunc vitae orci ultricies, auctor nunc in, volutpat nisl. Integer sit amet egestas eros,
vitae egestas augue. Duis vel est augue.
</Typography>
</ExpansionPanelDetails>
</ExpansionPanel>
*/
// {{
// this.props.button ?
// <Button
// style={{ marginTop: '20px' }}
// variant="contained"
// color="primary"
// onClick={() => { this.props.history.push(this.props.button.link) }}
// >
// {this.props.button.title}
// </Button>
// :
// <Button
// style={{ marginTop: '20px' }}
// variant="contained"
// color="primary"
// onClick={() => { this.props.history.push('/') }}
// >
// {Blockly.Msg.button_back}
// </Button>
// }}

View File

@ -11,7 +11,8 @@ import WorkspaceFunc from "./Workspace/WorkspaceFunc";
import BlocklyWindow from "./Blockly/BlocklyWindow";
import CodeViewer from "./CodeViewer";
import TrashcanButtons from "./Workspace/TrashcanButtons";
import HintTutorialExists from "./Tutorial/HintTutorialExists";
// import HintTutorialExists from "./Tutorial/HintTutorialExists";
import DeviceSelection from "./DeviceSelection";
import Grid from "@material-ui/core/Grid";
import IconButton from "@material-ui/core/IconButton";
@ -22,7 +23,7 @@ import { faCode } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import TooltipViewer from "./TooltipViewer";
import Dialog from "./Dialog";
// import Autosave from "./Workspace/AutoSave";
const styles = (theme) => ({
codeOn: {
backgroundColor: theme.palette.primary.main,
@ -54,11 +55,11 @@ class Home extends Component {
key: "",
message: "",
open: true,
initialXml: localStorage.getItem("autoSaveXML"),
};
}
componentDidMount() {
console.log(this.props.platform);
if (this.props.platform === true) {
this.setState({ codeOn: false });
}
@ -119,10 +120,12 @@ class Home extends Component {
<WorkspaceStats />
</div>
) : null}
<div
className="workspaceFunc"
style={{ float: "right", height: "40px", marginBottom: "20px" }}
>
{/* <Autosave /> */}
<WorkspaceFunc
project={this.props.project}
projectType={this.props.projectType}
@ -161,6 +164,7 @@ class Home extends Component {
<FontAwesomeIcon icon={faCode} size="xs" />
</IconButton>
</Tooltip>
<TrashcanButtons />
<div className="blocklyWindow">
{this.props.project ? (
@ -169,7 +173,10 @@ class Home extends Component {
initialXml={this.props.project.xml}
/>
) : (
<BlocklyWindow blocklyCSS={{ height: "80vH" }} />
<BlocklyWindow
blocklyCSS={{ height: "80vH" }}
initialXml={this.state.initialXml}
/>
)}
</div>
</Grid>
@ -180,7 +187,8 @@ class Home extends Component {
</Grid>
) : null}
</Grid>
<HintTutorialExists />
<DeviceSelection />
{/* <HintTutorialExists /> */}
{this.props.platform ? (
<Dialog
style={{ zIndex: 9999999 }}
@ -216,7 +224,7 @@ Home.propTypes = {
workspaceName: PropTypes.func.isRequired,
message: PropTypes.object.isRequired,
statistics: PropTypes.bool.isRequired,
platform: PropTypes.object.isRequired,
platform: PropTypes.bool.isRequired,
};
const mapStateToProps = (state) => ({

View File

@ -21,6 +21,7 @@ import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import LinearProgress from "@material-ui/core/LinearProgress";
import Tour from "reactour";
import { Badge } from "@material-ui/core";
import { home, assessment } from "./Tour";
import {
faBars,
@ -34,6 +35,8 @@ import {
faChalkboardTeacher,
faTools,
faLightbulb,
faCode,
faPuzzlePiece,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as Blockly from "blockly";
@ -211,6 +214,11 @@ class Navbar extends Component {
</div>
<List>
{[
{
text: Blockly.Msg.navbar_blockly,
icon: faPuzzlePiece,
link: "/",
},
{
text: Blockly.Msg.navbar_tutorials,
icon: faChalkboardTeacher,
@ -236,6 +244,11 @@ class Navbar extends Component {
link: "/project",
restriction: this.props.isAuthenticated,
},
{
text: "Code Editor",
icon: faCode,
link: "/codeeditor",
},
].map((item, index) => {
if (
item.restriction ||
@ -253,7 +266,13 @@ class Navbar extends Component {
<ListItemIcon>
<FontAwesomeIcon icon={item.icon} />
</ListItemIcon>
<ListItemText primary={item.text} />
{item.text === "Code Editor" ? (
<Badge badgeContent={"Experimental"} color="primary">
<ListItemText primary={item.text} />
</Badge>
) : (
<ListItemText primary={item.text} />
)}
</ListItem>
</Link>
);
@ -346,9 +365,9 @@ class Navbar extends Component {
Navbar.propTypes = {
tutorialIsLoading: PropTypes.bool.isRequired,
projectIsLoading: PropTypes.bool.isRequired,
isAuthenticated: PropTypes.bool.isRequired,
isAuthenticated: PropTypes.bool,
user: PropTypes.object,
tutorial: PropTypes.object.isRequired,
tutorial: PropTypes.object,
activeStep: PropTypes.number.isRequired,
};

View File

@ -1,44 +1,41 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Route, Redirect, withRouter } from 'react-router-dom';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { Route, Redirect, withRouter } from "react-router-dom";
class PrivateRoute extends Component {
render() {
return (
!this.props.progress ?
return !this.props.progress ? (
<Route
{...this.props.exact}
render={({ location }) =>
this.props.isAuthenticated ? (
this.props.children
) : (()=>{
return (
<Redirect
to={{
pathname: "/user/login",
state: { from: location }
}}
/>
)
})()
this.props.isAuthenticated
? this.props.children
: (() => {
return (
<Redirect
to={{
pathname: "/user/login",
state: { from: location },
}}
/>
);
})()
}
/> : null
);
/>
) : null;
}
}
PrivateRoute.propTypes = {
isAuthenticated: PropTypes.bool.isRequired,
progress: PropTypes.bool.isRequired
isAuthenticated: PropTypes.bool,
progress: PropTypes.bool.isRequired,
};
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
isAuthenticated: state.auth.isAuthenticated,
progress: state.auth.progress
progress: state.auth.progress,
});
export default connect(mapStateToProps, null)(withRouter(PrivateRoute));

View File

@ -24,6 +24,7 @@ import Login from "../User/Login";
import Account from "../User/Account";
import News from "../News";
import Faq from "../Faq";
import CodeEditor from "../CodeEditor/CodeEditor";
class Routes extends Component {
componentDidUpdate() {
@ -47,6 +48,9 @@ class Routes extends Component {
<Route path="/tutorial/:tutorialId" exact>
<Tutorial />
</Route>
<Route path="/CodeEditor" exact>
<CodeEditor />
</Route>
{/* Sharing */}
<PublicRoute path="/share/:shareId" exact>
<Project />
@ -100,7 +104,7 @@ class Routes extends Component {
}
Home.propTypes = {
visitPage: PropTypes.func.isRequired,
visitPage: PropTypes.func,
};
export default connect(null, { visitPage })(withRouter(Routes));

View File

@ -0,0 +1,55 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { setBoard } from '../../actions/boardAction';
import * as Blockly from 'blockly/core';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
import Typography from '@material-ui/core/Typography';
import FormHelperText from '@material-ui/core/FormHelperText';
class DeviceSelector extends Component {
componentDidMount(){
// Ensure that Blockly.setLocale is adopted in the component.
// Otherwise, the text will not be displayed until the next update of the component.
this.forceUpdate();
}
render(){
return(
<div>
<Typography style={{fontWeight: 'bold'}}>{Blockly.Msg.settings_board}</Typography>
<FormHelperText style={{color: 'black', lineHeight: 1.3, marginBottom: '8px'}}>{Blockly.Msg.settings_board_text}</FormHelperText>
<FormControl>
<InputLabel id="demo-simple-select-label">{Blockly.Msg.settings_board}</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={this.props.selectedBoard}
onChange={(e) => this.props.setBoard(e.target.value)}
>
<MenuItem value="mcu">senseBox MCU</MenuItem>
<MenuItem value="mini">senseBox MCU mini</MenuItem>
</Select>
</FormControl>
</div>
);
}
}
DeviceSelector.propTypes = {
setBoard: PropTypes.func.isRequired,
selectedBoard: PropTypes.string.isRequired
};
const mapStateToProps = state => ({
selectedBoard: state.board.board
});
export default connect(mapStateToProps, { setBoard })(DeviceSelector);

View File

@ -12,6 +12,7 @@ import RenderSelector from "./RenderSelector";
import StatsSelector from "./StatsSelector";
import OtaSelector from "./OtaSelector";
import SoundsSelector from "./SoundsSelector";
import DeviceSelector from "./DeviceSelector";
import Button from "@material-ui/core/Button";
import Paper from "@material-ui/core/Paper";
@ -52,6 +53,9 @@ class Settings extends Component {
<Paper style={{ margin: "10px 0px", padding: "10px" }}>
<SoundsSelector />
</Paper>
<Paper style={{ margin: "10px 0px", padding: "10px" }}>
<DeviceSelector />
</Paper>
<Button
style={{ marginTop: "10px" }}

View File

@ -52,7 +52,7 @@ class SoundsSelector extends Component {
SoundsSelector.propTypes = {
setSounds: PropTypes.func.isRequired,
language: PropTypes.string.isRequired,
sounds: PropTypes.string.isRequired,
sounds: PropTypes.bool.isRequired,
};
const mapStateToProps = (state) => ({

View File

@ -1,47 +1,55 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import withWidth from '@material-ui/core/withWidth';
import withWidth from "@material-ui/core/withWidth";
import { Card } from '@material-ui/core';
import * as Blockly from 'blockly'
import CardContent from '@material-ui/core/CardContent';
import { Card } from "@material-ui/core";
import * as Blockly from "blockly";
import CardContent from "@material-ui/core/CardContent";
import Typography from '@material-ui/core/Typography';
import ReactMarkdown from 'react-markdown';
import Typography from "@material-ui/core/Typography";
import ReactMarkdown from "react-markdown";
class TooltipViewer extends Component {
render() {
return (
<Card className="tooltipViewer" style={{ height: '100%', margin: '1vH 0 0 0', maxHeight: '19vH', overflow: 'auto' }} ref={this.myDiv}>
<Card
className="tooltipViewer"
style={{
height: "100%",
margin: "1vH 0 0 0",
maxHeight: "19vH",
overflow: "auto",
}}
ref={this.myDiv}
>
<CardContent>
<Typography variant="h5" component="h2">
{Blockly.Msg.tooltip_viewer}
</Typography>
<Typography variant="body2" component="p">
<ReactMarkdown linkTarget="_blank">{this.props.tooltip}</ReactMarkdown>
{this.props.helpurl !== '' ? <ReactMarkdown>{`${Blockly.Msg.tooltip_moreInformation} [${Blockly.Msg.labels_here}](${this.props.helpurl})`}</ReactMarkdown> : null}
<ReactMarkdown linkTarget="_blank">
{this.props.tooltip}
</ReactMarkdown>
</Typography>
{this.props.helpurl !== "" ? (
<ReactMarkdown>{`${Blockly.Msg.tooltip_moreInformation} [${Blockly.Msg.labels_here}](${this.props.helpurl})`}</ReactMarkdown>
) : null}
</CardContent>
</Card>
);
};
}
}
TooltipViewer.propTypes = {
tooltip: PropTypes.string.isRequired,
helpurl: PropTypes.string.isRequired
helpurl: PropTypes.string.isRequired,
};
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
tooltip: state.workspace.code.tooltip,
helpurl: state.workspace.code.helpurl
helpurl: state.workspace.code.helpurl,
});
export default connect(mapStateToProps, null)(withWidth()(TooltipViewer));

View File

@ -4,7 +4,6 @@ import { connect } from "react-redux";
import { workspaceName } from "../../actions/workspaceActions";
import BlocklyWindow from "../Blockly/BlocklyWindow";
import CodeViewer from "../CodeViewer";
import WorkspaceFunc from "../Workspace/WorkspaceFunc";
import withWidth, { isWidthDown } from "@material-ui/core/withWidth";
@ -13,8 +12,46 @@ import Card from "@material-ui/core/Card";
import Typography from "@material-ui/core/Typography";
import * as Blockly from "blockly";
import { initialXml } from "../Blockly/initialXml";
import IconButton from "@material-ui/core/IconButton";
import CodeViewer from "../CodeViewer";
import TooltipViewer from "../TooltipViewer";
import Tooltip from "@material-ui/core/Tooltip";
import ReactMarkdown from "react-markdown";
import { faCode } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { withStyles } from "@material-ui/core/styles";
import remarkGfm from "remark-gfm";
import remarkGemoji from "remark-gemoji";
const styles = (theme) => ({
codeOn: {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
"&:hover": {
backgroundColor: theme.palette.primary.contrastText,
color: theme.palette.primary.main,
border: `1px solid ${theme.palette.secondary.main}`,
},
},
codeOff: {
backgroundColor: theme.palette.primary.contrastText,
color: theme.palette.primary.main,
border: `1px solid ${theme.palette.secondary.main}`,
"&:hover": {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
},
},
});
class Assessment extends Component {
constructor(props) {
super(props);
this.state = {
codeOn: false,
};
}
componentDidMount() {
this.props.workspaceName(this.props.name);
}
@ -25,6 +62,10 @@ class Assessment extends Component {
}
}
onChange = () => {
this.setState({ codeOn: !this.state.codeOn });
};
render() {
var tutorialId = this.props.tutorial._id;
var currentTask = this.props.step;
@ -35,63 +76,98 @@ class Assessment extends Component {
(task) => task._id === currentTask._id
);
var statusTask = status.tasks[taskIndex];
console.log(statusTask);
return (
<div className="assessmentDiv" style={{ width: "100%" }}>
<Typography
variant="h4"
style={{
float: "left",
marginBottom: "5px",
height: "40px",
display: "table",
}}
>
{currentTask.headline}
</Typography>
<div style={{ float: "right", height: "40px" }}>
<WorkspaceFunc assessment />
</div>
<Grid container spacing={2} style={{ marginBottom: "5px" }}>
<Grid item xs={12} md={6} lg={8}>
<Grid
item
xs={12}
md={this.state.codeOn ? 6 : 9}
lg={this.state.codeOn ? 6 : 9}
style={{ position: "relative" }}
>
<Tooltip
title={
this.state.codeOn
? Blockly.Msg.tooltip_hide_code
: Blockly.Msg.tooltip_show_code
}
>
<IconButton
className={`showCode ${
this.state.codeOn
? this.props.classes.codeOn
: this.props.classes.codeOff
}`}
style={{
width: "40px",
height: "40px",
position: "absolute",
top: 6,
right: 8,
zIndex: 21,
}}
onClick={() => this.onChange()}
>
<FontAwesomeIcon icon={faCode} size="xs" />
</IconButton>
</Tooltip>
<BlocklyWindow
initialXml={initialXml}
blockDisabled
blocklyCSS={{ height: "65vH" }}
/>
</Grid>
{this.state.codeOn ? (
<Grid item xs={12} md={4} lg={3}>
<CodeViewer />
</Grid>
) : null}
<Grid
item
xs={12}
md={6}
lg={4}
style={
isWidthDown("sm", this.props.width)
? { height: "max-content" }
: {}
}
md={3}
lg={3}
style={{
position: "relative",
// isWidthDown("sm", this.props.width)
// ? { height: "max-content" }
// : {}
}}
>
<Card
style={{
height: "calc(50% - 30px)",
height: "calc(44vH - 35px)",
padding: "10px",
marginBottom: "10px",
}}
>
<Typography variant="h5">
{Blockly.Msg.tutorials_assessment_task}
<Typography>
<ReactMarkdown remarkPlugins={[remarkGfm, remarkGemoji]}>
{currentTask.text}
</ReactMarkdown>
</Typography>
<Typography>{currentTask.text}</Typography>
</Card>
{/* <Card
style={{
height: "20vH",
padding: "10px",
marginBottom: "10px",
}}
> */}
<TooltipViewer />
{/* </Card> */}
<div
style={
isWidthDown("sm", this.props.width)
? { height: "500px" }
: { height: "50%" }
}
>
<CodeViewer />
</div>
></div>
</Grid>
</Grid>
</div>
@ -113,5 +189,5 @@ const mapStateToProps = (state) => ({
});
export default connect(mapStateToProps, { workspaceName })(
withWidth()(Assessment)
withWidth()(withStyles(styles, { withTheme: true })(Assessment))
);

View File

@ -1,53 +1,57 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { changeContent, deleteProperty, setError, deleteError } from '../../../actions/tutorialBuilderActions';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import {
changeContent,
deleteProperty,
setError,
deleteError,
} from "../../../actions/tutorialBuilderActions";
import moment from 'moment';
import localization from 'moment/locale/de';
import * as Blockly from 'blockly/core';
import moment from "moment";
import localization from "moment/locale/de";
import * as Blockly from "blockly/core";
import { initialXml } from '../../Blockly//initialXml.js';
import BlocklyWindow from '../../Blockly/BlocklyWindow';
import { initialXml } from "../../Blockly//initialXml.js";
import BlocklyWindow from "../../Blockly/BlocklyWindow";
import { withStyles } from '@material-ui/core/styles';
import Switch from '@material-ui/core/Switch';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormLabel from '@material-ui/core/FormLabel';
import Button from '@material-ui/core/Button';
import Grid from '@material-ui/core/Grid';
import { withStyles } from "@material-ui/core/styles";
import Switch from "@material-ui/core/Switch";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormHelperText from "@material-ui/core/FormHelperText";
import FormLabel from "@material-ui/core/FormLabel";
import Button from "@material-ui/core/Button";
import Grid from "@material-ui/core/Grid";
const styles = (theme) => ({
errorColor: {
color: theme.palette.error.dark
color: theme.palette.error.dark,
},
errorBorder: {
border: `1px solid ${theme.palette.error.dark}`
border: `1px solid ${theme.palette.error.dark}`,
},
errorButton: {
marginTop: '5px',
height: '40px',
marginTop: "5px",
height: "40px",
backgroundColor: theme.palette.error.dark,
'&:hover': {
backgroundColor: theme.palette.error.dark
}
}
"&:hover": {
backgroundColor: theme.palette.error.dark,
},
},
});
class BlocklyExample extends Component {
constructor(props) {
super(props);
this.state = {
checked: props.task ? props.task : props.value ? true : false,
input: null,
disabled: false
disabled: false,
};
}
componentDidMount() {
moment.updateLocale('de', localization);
moment.updateLocale("de", localization);
this.isError();
// if(this.props.task){
// this.props.setError(this.props.index, 'xml');
@ -56,7 +60,14 @@ class BlocklyExample extends Component {
componentDidUpdate(props, state) {
if (props.task !== this.props.task || props.value !== this.props.value) {
this.setState({ checked: this.props.task ? this.props.task : this.props.value ? true : false },
this.setState(
{
checked: this.props.task
? this.props.task
: this.props.value
? true
: false,
},
() => this.isError()
);
}
@ -77,12 +88,11 @@ class BlocklyExample extends Component {
// check if value is valid xml;
try {
Blockly.Xml.textToDom(xml);
this.props.deleteError(this.props.index, 'xml');
}
catch (err) {
this.props.deleteError(this.props.index, "xml");
} catch (err) {
xml = initialXml;
// not valid xml, throw error in redux store
this.props.setError(this.props.index, 'xml');
this.props.setError(this.props.index, "xml");
}
if (!this.props.task) {
// instruction can also display only one block, which does not necessarily
@ -90,31 +100,38 @@ class BlocklyExample extends Component {
xml = xml.replace('deletable="false"', 'deletable="true"');
}
this.setState({ xml: xml });
} else {
this.props.deleteError(this.props.index, "xml");
}
else {
this.props.deleteError(this.props.index, 'xml');
}
}
};
onChange = (value) => {
var oldValue = this.state.checked;
this.setState({ checked: value });
if (oldValue !== value && !value) {
this.props.deleteError(this.props.index, 'xml');
this.props.deleteProperty(this.props.index, 'xml');
this.props.deleteError(this.props.index, "xml");
this.props.deleteProperty(this.props.index, "xml");
}
}
};
setXml = () => {
var xml = this.props.xml;
this.props.changeContent(xml, this.props.index, 'xml');
this.setState({ input: moment(Date.now()).format('LTS') });
}
this.props.changeContent(xml, this.props.index, "xml");
this.setState({ input: moment(Date.now()).format("LTS") });
};
render() {
return (
<div style={{ marginBottom: '10px', padding: '18.5px 14px', borderRadius: '25px', border: '1px solid lightgrey', width: 'calc(100% - 28px)' }}>
{!this.props.task ?
<div
style={{
marginBottom: "10px",
padding: "18.5px 14px",
borderRadius: "25px",
border: "1px solid lightgrey",
width: "calc(100% - 28px)",
}}
>
{!this.props.task ? (
<FormControlLabel
labelPlacement="end"
label={"Blockly Beispiel"}
@ -126,45 +143,77 @@ class BlocklyExample extends Component {
/>
}
/>
: <FormLabel style={{ color: 'black' }}>{Blockly.Msg.builder_solution}</FormLabel>}
{this.state.checked ? !this.props.value || this.props.error ?
<FormHelperText style={{ lineHeight: 'initial' }} className={this.props.classes.errorColor}>{`Reiche deine Blöcke ein, indem du auf den '${this.props.task ? Blockly.Msg.builder_solution_submit : Blockly.Msg.builder_example_submit}'-Button klickst.`}</FormHelperText>
: this.state.input ? <FormHelperText style={{ lineHeight: 'initial' }}>Die letzte Einreichung erfolgte um {this.state.input} Uhr.</FormHelperText> : null
: null}
{this.state.checked && !this.props.task ?
<FormHelperText style={{ lineHeight: 'initial' }}>{Blockly.Msg.builder_comment}</FormHelperText>
: null}
) : (
<FormLabel style={{ color: "black" }}>
{Blockly.Msg.builder_solution}
</FormLabel>
)}
{this.state.checked ? (
!this.props.value || this.props.error ? (
<FormHelperText
style={{ lineHeight: "initial" }}
className={this.props.classes.errorColor}
>{`Reiche deine Blöcke ein, indem du auf den '${
this.props.task
? Blockly.Msg.builder_solution_submit
: Blockly.Msg.builder_example_submit
}'-Button klickst.`}</FormHelperText>
) : this.state.input ? (
<FormHelperText style={{ lineHeight: "initial" }}>
Die letzte Einreichung erfolgte um {this.state.input} Uhr.
</FormHelperText>
) : null
) : null}
{this.state.checked && !this.props.task ? (
<FormHelperText style={{ lineHeight: "initial" }}>
{Blockly.Msg.builder_comment}
</FormHelperText>
) : null}
{/* ensure that the correct xml-file is displayed in the workspace */}
{this.state.checked && this.state.xml ? (() => {
return (
<div style={{ marginTop: '10px' }}>
<Grid container className={!this.props.value || this.props.error ? this.props.classes.errorBorder : null}>
<Grid item xs={12}>
<BlocklyWindow
blockDisabled={this.props.task}
trashcan={false}
initialXml={this.state.xml}
blocklyCSS={{ height: '500px' }}
/>
</Grid>
</Grid>
<Button
className={!this.props.value || this.props.error ? this.props.classes.errorButton : null}
style={{ marginTop: '5px', height: '40px' }}
variant='contained'
color='primary'
disabled={this.state.disabled}
onClick={() => this.setXml()}
>
{this.props.task ? Blockly.Msg.builder_solution_submit : Blockly.Msg.builder_example_submit}
</Button>
</div>
)
})()
{this.state.checked && this.state.xml
? (() => {
return (
<div style={{ marginTop: "10px" }}>
<Grid
container
className={
!this.props.value || this.props.error
? this.props.classes.errorBorder
: null
}
>
<Grid item xs={12}>
<BlocklyWindow
blockDisabled={this.props.task}
trashcan={false}
initialXml={this.state.xml}
blocklyCSS={{ height: "500px" }}
/>
</Grid>
</Grid>
<Button
className={
!this.props.value || this.props.error
? this.props.classes.errorButton
: null
}
style={{ marginTop: "5px", height: "40px" }}
variant="contained"
color="primary"
disabled={this.state.disabled}
onClick={() => this.setXml()}
>
{this.props.task
? Blockly.Msg.builder_solution_submit
: Blockly.Msg.builder_example_submit}
</Button>
</div>
);
})()
: null}
</div>
);
};
}
}
BlocklyExample.propTypes = {
@ -172,12 +221,16 @@ BlocklyExample.propTypes = {
deleteProperty: PropTypes.func.isRequired,
setError: PropTypes.func.isRequired,
deleteError: PropTypes.func.isRequired,
xml: PropTypes.string.isRequired
xml: PropTypes.string.isRequired,
};
const mapStateToProps = state => ({
xml: state.workspace.code.xml
const mapStateToProps = (state) => ({
xml: state.workspace.code.xml,
});
export default connect(mapStateToProps, { changeContent, deleteProperty, setError, deleteError })(withStyles(styles, { withTheme: true })(BlocklyExample));
export default connect(mapStateToProps, {
changeContent,
deleteProperty,
setError,
deleteError,
})(withStyles(styles, { withTheme: true })(BlocklyExample));

View File

@ -10,6 +10,8 @@ import {
resetTutorial as resetTutorialBuilder,
} from "../../../actions/tutorialBuilderActions";
import {
getAllTutorials,
getUserTutorials,
getTutorials,
resetTutorial,
deleteTutorial,
@ -19,12 +21,17 @@ import { clearMessages } from "../../../actions/messageActions";
import axios from "axios";
import { withRouter } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faEyeSlash, faUserCheck } from "@fortawesome/free-solid-svg-icons";
import Breadcrumbs from "../../Breadcrumbs";
import Textfield from "./Textfield";
import Difficulty from "./Difficulty";
import Step from "./Step";
import Dialog from "../../Dialog";
import Snackbar from "../../Snackbar";
import Public from "./Public";
import Review from "./Review";
import { withStyles } from "@material-ui/core/styles";
import Button from "@material-ui/core/Button";
@ -66,6 +73,8 @@ class Builder extends Component {
tutorial: "new",
open: false,
title: "",
public: false,
difficulty: "",
content: "",
string: false,
snackbar: false,
@ -80,7 +89,11 @@ class Builder extends Component {
// retrieve tutorials only if a potential user is loaded - authentication
// is finished (success or failed)
if (!this.props.authProgress) {
this.props.getTutorials();
if (this.props.user.role === "admin") {
this.props.getAllTutorials();
} else {
this.props.getUserTutorials();
}
}
}
@ -89,8 +102,12 @@ class Builder extends Component {
props.authProgress !== this.props.authProgress &&
!this.props.authProgress
) {
// authentication is completed
this.props.getTutorials();
if (this.props.user.role === "admin") {
// authentication is completed
this.props.getAllTutorials();
} else {
this.props.getUserTutorials();
}
}
if (props.message !== this.props.message) {
if (this.props.message.id === "GET_TUTORIALS_FAIL") {
@ -258,6 +275,9 @@ class Builder extends Component {
var steps = this.props.steps;
var newTutorial = new FormData();
newTutorial.append("title", this.props.title);
newTutorial.append("difficulty", this.props.difficulty);
newTutorial.append("public", this.props.public);
newTutorial.append("review", this.props.review);
steps.forEach((step, i) => {
if (step._id) {
newTutorial.append(`steps[${i}][_id]`, step._id);
@ -283,21 +303,6 @@ class Builder extends Component {
// optional
newTutorial.append(`steps[${i}][xml]`, step.xml);
}
if (step.media) {
// optional
if (step.media.youtube) {
newTutorial.append(
`steps[${i}][media][youtube]`,
step.media.youtube
);
}
if (step.media.picture) {
newTutorial.append(
`steps[${i}][media][picture]`,
step.media.picture
);
}
}
});
return newTutorial;
}
@ -370,9 +375,19 @@ class Builder extends Component {
};
render() {
var filteredTutorials = this.props.tutorials.filter(
(tutorial) => tutorial.creator === this.props.user.email
);
if (this.props.user.role === "admin") {
var filteredTutorials = this.props.tutorials;
} else {
filteredTutorials = this.props.tutorials.filter(
(tutorial) => tutorial.creator === this.props.user.email
);
}
// } else {
// filteredTutorials = this.props.userTutorials.filter(
// (tutorial) => tutorial.creator === this.props.user.email
// );
return (
<div>
<Breadcrumbs
@ -462,7 +477,24 @@ class Builder extends Component {
label="Tutorial"
>
{filteredTutorials.map((tutorial) => (
<MenuItem value={tutorial._id}>{tutorial.title}</MenuItem>
<MenuItem value={tutorial._id}>
{tutorial.title}{" "}
{tutorial.review && tutorial.public === false ? (
<div>
<FontAwesomeIcon icon={faUserCheck} />
<FontAwesomeIcon icon={faEyeSlash} />
</div>
) : tutorial.public === false ? (
<FontAwesomeIcon icon={faEyeSlash} />
) : null}
</MenuItem>
/* ) : tutorial.public === false ? (
<MenuItem value={tutorial._id}>
{tutorial.title} <FontAwesomeIcon icon={faEyeSlash} />
</MenuItem>
) : (
<MenuItem value={tutorial._id}>{tutorial.title}</MenuItem>
)} */
))}
</Select>
</FormControl>
@ -487,6 +519,45 @@ class Builder extends Component {
label={"Titel"}
error={this.props.error.title}
/>
<div
style={{
borderRadius: "25px",
border: "1px solid lightgrey",
padding: "10px 14px 10px 10px",
marginBottom: "20px",
}}
>
<Difficulty
value={this.props.difficulty}
property={"difficulty"}
label={"difficulty"}
error={this.props.error.difficulty}
/>
<Divider
variant="fullWidth"
style={{ margin: "30px 0 10px 0" }}
/>
<Review
value={this.props.review}
property={"review"}
label={"review"}
error={this.props.error.review}
/>
<Divider
variant="fullWidth"
style={{ margin: "30px 0 10px 0" }}
/>
{this.props.user.blocklyRole === "admin" ? (
<Public
value={this.props.public}
property={"public"}
label={"public"}
error={this.props.error.public}
/>
) : null}
</div>
{this.props.steps.map((step, i) => (
<Step step={step} index={i} key={i} />
@ -619,6 +690,8 @@ class Builder extends Component {
}
Builder.propTypes = {
getAllTutorials: PropTypes.func.isRequired,
getUserTutorials: PropTypes.func.isRequired,
getTutorials: PropTypes.func.isRequired,
resetTutorial: PropTypes.func.isRequired,
clearMessages: PropTypes.func.isRequired,
@ -631,6 +704,9 @@ Builder.propTypes = {
resetTutorialBuilder: PropTypes.func.isRequired,
tutorialProgress: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
difficulty: PropTypes.number.isRequired,
public: PropTypes.bool.isRequired,
review: PropTypes.bool.isRequired,
id: PropTypes.string.isRequired,
steps: PropTypes.array.isRequired,
change: PropTypes.number.isRequired,
@ -645,12 +721,16 @@ Builder.propTypes = {
const mapStateToProps = (state) => ({
title: state.builder.title,
difficulty: state.builder.difficulty,
review: state.builder.review,
public: state.builder.public,
id: state.builder.id,
steps: state.builder.steps,
change: state.builder.change,
error: state.builder.error,
json: state.builder.json,
isProgress: state.builder.progress,
userTutorials: state.tutorial.userTutorials,
tutorials: state.tutorial.tutorials,
message: state.message,
user: state.auth.user,
@ -665,6 +745,8 @@ export default connect(mapStateToProps, {
tutorialId,
resetTutorialBuilder,
getTutorials,
getUserTutorials,
getAllTutorials,
resetTutorial,
tutorialProgress,
clearMessages,

View File

@ -0,0 +1,99 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import {
tutorialDifficulty,
jsonString,
changeContent,
setError,
deleteError,
} from "../../../actions/tutorialBuilderActions";
import { withStyles } from "@material-ui/core/styles";
import ReactStars from "react-rating-stars-component";
import * as Blockly from "blockly";
import FormGroup from "@material-ui/core/FormGroup";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormControl from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";
const styles = (theme) => ({
multiline: {
padding: "18.5px 14px 18.5px 24px",
},
errorColor: {
color: `${theme.palette.error.dark} !important`,
},
errorColorShrink: {
color: `rgba(0, 0, 0, 0.54) !important`,
},
errorBorder: {
borderColor: `${theme.palette.error.dark} !important`,
},
});
class Difficulty extends Component {
ratingChanged = (newRating) => {
this.handleChange(newRating);
};
handleChange = (e) => {
var value = e;
if (this.props.property === "difficulty") {
this.props.tutorialDifficulty(value);
} else if (this.props.property === "json") {
this.props.jsonString(value);
} else {
this.props.changeContent(
value,
this.props.index,
this.props.property,
this.props.property2
);
}
};
render() {
return (
<FormControl component="fieldset">
<FormLabel component="legend">
{Blockly.Msg.builder_difficulty}
</FormLabel>
<FormGroup aria-label="position" row>
<FormControlLabel
value="Tutorial veröffentlichen"
control={
<ReactStars
count={5}
onChange={this.handleChange}
value={this.props.value}
size={30}
isHalf={true}
emptyIcon={<i className="fa fa-star"></i>}
halfIcon={<i className="fa fa-star-half-alt"></i>}
fullIcon={<i className="fa fa-star"></i>}
activeColor="#ffd700"
/>
}
label="Schwierigkeitsgrad"
labelPlacement="start"
/>
</FormGroup>
</FormControl>
);
}
}
Difficulty.propTypes = {
tutorialDifficulty: PropTypes.func.isRequired,
jsonString: PropTypes.func.isRequired,
changeContent: PropTypes.func.isRequired,
};
export default connect(null, {
tutorialDifficulty,
jsonString,
changeContent,
setError,
deleteError,
})(withStyles(styles, { withTheme: true })(Difficulty));

View File

@ -1,107 +1,159 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { changeContent, setError, deleteError } from '../../../actions/tutorialBuilderActions';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import {
changeContent,
setError,
deleteError,
} from "../../../actions/tutorialBuilderActions";
import hardware from '../../../data/hardware.json';
import hardware from "../../../data/hardware.json";
import { withStyles } from '@material-ui/core/styles';
import withWidth, { isWidthDown } from '@material-ui/core/withWidth';
import GridList from '@material-ui/core/GridList';
import GridListTile from '@material-ui/core/GridListTile';
import GridListTileBar from '@material-ui/core/GridListTileBar';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormLabel from '@material-ui/core/FormLabel';
import * as Blockly from 'blockly'
import { withStyles } from "@material-ui/core/styles";
import withWidth, { isWidthDown } from "@material-ui/core/withWidth";
import ImageList from "@material-ui/core/ImageList";
import ImageListItem from "@material-ui/core/ImageListItem";
import ImageListItemBar from "@material-ui/core/ImageListItemBar";
import FormHelperText from "@material-ui/core/FormHelperText";
import FormLabel from "@material-ui/core/FormLabel";
import * as Blockly from "blockly";
const styles = theme => ({
multiGridListTile: {
const styles = (theme) => ({
multiImageListItem: {
background: theme.palette.primary.main,
opacity: 0.9,
height: '30px'
height: "30px",
},
multiGridListTileTitle: {
color: theme.palette.text.primary
multiImageListItemTitle: {
color: theme.palette.text.primary,
},
border: {
cursor: 'pointer',
'&:hover': {
width: 'calc(100% - 4px)',
height: 'calc(100% - 4px)',
border: `2px solid ${theme.palette.primary.main}`
}
cursor: "pointer",
"&:hover": {
width: "calc(100% - 4px)",
height: "calc(100% - 4px)",
border: `2px solid ${theme.palette.primary.main}`,
},
},
active: {
cursor: 'pointer',
width: 'calc(100% - 4px)',
height: 'calc(100% - 4px)',
border: `2px solid ${theme.palette.primary.main}`
cursor: "pointer",
width: "calc(100% - 4px)",
height: "calc(100% - 4px)",
border: `2px solid ${theme.palette.primary.main}`,
},
errorColor: {
color: theme.palette.error.dark,
lineHeight: 'initial',
marginBottom: '10px'
}
lineHeight: "initial",
marginBottom: "10px",
},
});
class Requirements extends Component {
onChange = (hardware) => {
var hardwareArray = this.props.value;
if (hardwareArray.filter(value => value === hardware).length > 0) {
hardwareArray = hardwareArray.filter(value => value !== hardware);
}
else {
if (hardwareArray.filter((value) => value === hardware).length > 0) {
hardwareArray = hardwareArray.filter((value) => value !== hardware);
} else {
hardwareArray.push(hardware);
if (this.props.error) {
this.props.deleteError(this.props.index, 'hardware');
this.props.deleteError(this.props.index, "hardware");
}
}
this.props.changeContent(hardwareArray, this.props.index, 'hardware');
this.props.changeContent(hardwareArray, this.props.index, "hardware");
if (hardwareArray.length === 0) {
this.props.setError(this.props.index, 'hardware');
this.props.setError(this.props.index, "hardware");
}
}
};
render() {
var cols = isWidthDown('md', this.props.width) ? isWidthDown('sm', this.props.width) ? isWidthDown('xs', this.props.width) ? 2 : 3 : 4 : 6;
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' }}>{Blockly.Msg.builder_hardware_order}</FormHelperText>
{this.props.error ? <FormHelperText className={this.props.classes.errorColor}>{Blockly.Msg.builder_hardware_helper}</FormHelperText> : null}
<GridList cellHeight={100} cols={cols} spacing={10}>
<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",
}
}
>
{Blockly.Msg.builder_hardware_order}
</FormHelperText>
{this.props.error ? (
<FormHelperText className={this.props.classes.errorColor}>
{Blockly.Msg.builder_hardware_helper}
</FormHelperText>
) : null}
<ImageList rowHeight={100} cols={cols} gap={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} />
<ImageListItem
key={i}
onClick={() => this.onChange(picture.id)}
classes={{
item:
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 }}
<ImageListItemBar
classes={{ root: this.props.classes.multiImageListItem }}
title={
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }} className={this.props.classes.multiGridListTileTitle}>
<div
style={{ overflow: "hidden", textOverflow: "ellipsis" }}
className={this.props.classes.multiImageListItemTitle}
>
{picture.name}
</div>
}
/>
</GridListTile>
</ImageListItem>
))}
</GridList>
</ImageList>
</div>
);
};
}
}
Requirements.propTypes = {
changeContent: PropTypes.func.isRequired,
setError: PropTypes.func.isRequired,
deleteError: PropTypes.func.isRequired,
change: PropTypes.number.isRequired
change: PropTypes.number.isRequired,
};
const mapStateToProps = state => ({
change: state.builder.change
const mapStateToProps = (state) => ({
change: state.builder.change,
});
export default connect(mapStateToProps, { changeContent, setError, deleteError })(withStyles(styles, { withTheme: true })(withWidth()(Requirements)));
export default connect(mapStateToProps, {
changeContent,
setError,
deleteError,
})(withStyles(styles, { withTheme: true })(withWidth()(Requirements)));

View File

@ -0,0 +1,80 @@
import React from "react";
import { connect } from "react-redux";
import {
tutorialTitle,
jsonString,
changeContent,
setError,
deleteError,
} from "../../../actions/tutorialBuilderActions";
import FormControl from "@material-ui/core/FormControl";
import MarkdownIt from "markdown-it";
import Editor from "react-markdown-editor-lite";
import "react-markdown-editor-lite/lib/index.css";
import axios from "axios";
const mdParser = new MarkdownIt(/* Markdown-it options */);
const MarkdownEditor = (props) => {
const [value, setValue] = React.useState(props.value);
const mdEditor = React.useRef(null);
function handleChange({ html, text }) {
setValue(text);
var value = text;
props.changeContent(value, props.index, props.property, props.property2);
if (value.replace(/\s/g, "") === "") {
props.setError(props.index, props.property);
} else {
props.deleteError(props.index, props.property);
}
}
async function uploadImage(files) {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append("files", files);
axios({
method: "post",
url: `${process.env.REACT_APP_BLOCKLY_API}/upload/uploadImage`,
data: formData,
headers: { "Content-Type": "multipart/form-data" },
})
.then((res) => {
resolve(
`${process.env.REACT_APP_BLOCKLY_API}/upload/` + res.data.filename
);
})
.catch((err) => {
reject(new Error("error"));
});
});
}
return (
<FormControl variant="outlined" fullWidth style={{ marginBottom: "10px" }}>
<Editor
ref={mdEditor}
style={{ height: "500px" }}
renderHTML={(text) => mdParser.render(text)}
onChange={handleChange}
value={value}
id={props.property}
label={props.label}
property={props.property}
onImageUpload={uploadImage}
/>
</FormControl>
);
};
export default connect(null, {
tutorialTitle,
jsonString,
changeContent,
setError,
deleteError,
})(MarkdownEditor);

View File

@ -0,0 +1,92 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import {
tutorialPublic,
jsonString,
changeContent,
setError,
deleteError,
} from "../../../actions/tutorialBuilderActions";
import { withStyles } from "@material-ui/core/styles";
import * as Blockly from "blockly";
import Checkbox from "@material-ui/core/Checkbox";
import FormGroup from "@material-ui/core/FormGroup";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormControl from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";
const styles = (theme) => ({
multiline: {
padding: "18.5px 14px 18.5px 24px",
},
errorColor: {
color: `${theme.palette.error.dark} !important`,
},
errorColorShrink: {
color: `rgba(0, 0, 0, 0.54) !important`,
},
errorBorder: {
borderColor: `${theme.palette.error.dark} !important`,
},
});
class Public extends Component {
handleChange = (e) => {
var value = e.target.checked;
if (this.props.property === "public") {
this.props.tutorialPublic(value);
} else if (this.props.property === "json") {
this.props.jsonString(value);
} else {
this.props.changeContent(
value,
this.props.index,
this.props.property,
this.props.property2
);
}
};
render() {
return (
<FormControl component="fieldset">
<FormLabel component="legend">
{Blockly.Msg.builder_public_head}
</FormLabel>
<FormGroup aria-label="position" row>
<FormControlLabel
value="Tutorial veröffentlichen"
control={
<Checkbox
checked={this.props.value}
onChange={this.handleChange}
color="primary"
name="checkedA"
inputProps={{ "aria-label": "secondary checkbox" }}
/>
}
label={Blockly.Msg.builder_public_label}
labelPlacement="start"
/>
</FormGroup>
</FormControl>
);
}
}
Public.propTypes = {
tutorialPublic: PropTypes.func.isRequired,
jsonString: PropTypes.func.isRequired,
changeContent: PropTypes.func.isRequired,
};
export default connect(null, {
tutorialPublic,
jsonString,
changeContent,
setError,
deleteError,
})(withStyles(styles, { withTheme: true })(Public));

View File

@ -0,0 +1,93 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import {
tutorialReview,
jsonString,
changeContent,
setError,
deleteError,
} from "../../../actions/tutorialBuilderActions";
import { withStyles } from "@material-ui/core/styles";
import * as Blockly from "blockly";
import Checkbox from "@material-ui/core/Checkbox";
import FormGroup from "@material-ui/core/FormGroup";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormControl from "@material-ui/core/FormControl";
import FormLabel from "@material-ui/core/FormLabel";
const styles = (theme) => ({
multiline: {
padding: "18.5px 14px 18.5px 24px",
},
errorColor: {
color: `${theme.palette.error.dark} !important`,
},
errorColorShrink: {
color: `rgba(0, 0, 0, 0.54) !important`,
},
errorBorder: {
borderColor: `${theme.palette.error.dark} !important`,
},
});
class Review extends Component {
handleChange = (e) => {
var value = e.target.checked;
if (this.props.property === "review") {
this.props.tutorialReview(value);
} else if (this.props.property === "json") {
this.props.jsonString(value);
} else {
this.props.changeContent(
value,
this.props.index,
this.props.property,
this.props.property2
);
}
};
render() {
return (
<FormControl component="fieldset">
<FormLabel component="legend">
{Blockly.Msg.builder_review_head}
</FormLabel>
{Blockly.Msg.builder_review_text}
<FormGroup aria-label="position" row>
<FormControlLabel
value="Tutorial veröffentlichen"
control={
<Checkbox
checked={this.props.value}
onChange={this.handleChange}
color="primary"
name="checkedA"
inputProps={{ "aria-label": "secondary checkbox" }}
/>
}
label="Ich möchte mein Tutorial öffentlich machen"
labelPlacement="start"
/>
</FormGroup>
</FormControl>
);
}
}
Review.propTypes = {
tutorialReview: PropTypes.func.isRequired,
jsonString: PropTypes.func.isRequired,
changeContent: PropTypes.func.isRequired,
};
export default connect(null, {
tutorialReview,
jsonString,
changeContent,
setError,
deleteError,
})(withStyles(styles, { withTheme: true })(Review));

View File

@ -1,118 +1,194 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { addStep, removeStep, changeStepIndex } from '../../../actions/tutorialBuilderActions';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import {
addStep,
removeStep,
changeStepIndex,
} from "../../../actions/tutorialBuilderActions";
import clsx from 'clsx';
import clsx from "clsx";
import Textfield from './Textfield';
import StepType from './StepType';
import BlocklyExample from './BlocklyExample';
import Requirements from './Requirements';
import Hardware from './Hardware';
import Media from './Media';
import Textfield from "./Textfield";
import StepType from "./StepType";
import BlocklyExample from "./BlocklyExample";
import Requirements from "./Requirements";
import Hardware from "./Hardware";
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import { withStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import IconButton from "@material-ui/core/IconButton";
import Tooltip from "@material-ui/core/Tooltip";
import { faPlus, faAngleDoubleUp, faAngleDoubleDown, faTrash } from "@fortawesome/free-solid-svg-icons";
import {
faPlus,
faAngleDoubleUp,
faAngleDoubleDown,
faTrash,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import MarkdownEditor from "./MarkdownEditor";
const styles = (theme) => ({
button: {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
width: '40px',
height: '40px',
'&:hover': {
width: "40px",
height: "40px",
"&:hover": {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
}
},
},
delete: {
backgroundColor: theme.palette.error.dark,
color: theme.palette.error.contrastText,
'&:hover': {
"&:hover": {
backgroundColor: theme.palette.error.dark,
color: theme.palette.error.contrastText,
}
}
},
},
});
class Step extends Component {
render() {
var index = this.props.index;
var steps = this.props.steps;
return (
<div style={{borderRadius: '25px', border: '1px solid lightgrey', padding: '10px 14px 10px 10px', marginBottom: '20px'}}>
<Typography variant='h6' style={{marginBottom: '10px', marginLeft: '4px'}}>Schritt {index+1}</Typography>
<div style={{display: 'flex', position: 'relative'}}>
<div style={{width: '40px', marginRight: '10px', position: 'absolute', left: '4px', bottom: '10px'}}>
<Tooltip title='Schritt hinzufügen' arrow>
<div
style={{
borderRadius: "25px",
border: "1px solid lightgrey",
padding: "10px 14px 10px 10px",
marginBottom: "20px",
}}
>
<Typography
variant="h6"
style={{ marginBottom: "10px", marginLeft: "4px" }}
>
Schritt {index + 1}
</Typography>
<div style={{ display: "flex", position: "relative" }}>
<div
style={{
width: "40px",
marginRight: "10px",
position: "absolute",
left: "4px",
bottom: "10px",
}}
>
<Tooltip title="Schritt hinzufügen" arrow>
<IconButton
className={this.props.classes.button}
style={index === 0 ? {} : {marginBottom: '5px'}}
onClick={() => this.props.addStep(index+1)}
style={index === 0 ? {} : { marginBottom: "5px" }}
onClick={() => this.props.addStep(index + 1)}
>
<FontAwesomeIcon icon={faPlus} size="xs"/>
<FontAwesomeIcon icon={faPlus} size="xs" />
</IconButton>
</Tooltip>
{index !== 0 ?
{index !== 0 ? (
<div>
<Tooltip title={`Schritt ${index+1} nach oben schieben`} arrow>
<Tooltip
title={`Schritt ${index + 1} nach oben schieben`}
arrow
>
<IconButton
disabled={index < 2}
className={this.props.classes.button}
style={{marginBottom: '5px'}}
onClick={() => this.props.changeStepIndex(index, index-1)}
style={{ marginBottom: "5px" }}
onClick={() => this.props.changeStepIndex(index, index - 1)}
>
<FontAwesomeIcon icon={faAngleDoubleUp} size="xs"/>
<FontAwesomeIcon icon={faAngleDoubleUp} size="xs" />
</IconButton>
</Tooltip>
<Tooltip title={`Schritt ${index+1} nach unten schieben`} arrow>
<Tooltip
title={`Schritt ${index + 1} nach unten schieben`}
arrow
>
<IconButton
disabled={index === steps.length-1}
disabled={index === steps.length - 1}
className={this.props.classes.button}
style={{marginBottom: '5px'}}
onClick={() => this.props.changeStepIndex(index, index+1)}
style={{ marginBottom: "5px" }}
onClick={() => this.props.changeStepIndex(index, index + 1)}
>
<FontAwesomeIcon icon={faAngleDoubleDown} size="xs"/>
<FontAwesomeIcon icon={faAngleDoubleDown} size="xs" />
</IconButton>
</Tooltip>
<Tooltip title={`Schritt ${index+1} löschen`} arrow>
<Tooltip title={`Schritt ${index + 1} löschen`} arrow>
<IconButton
disabled={index === 0}
className={clsx(this.props.classes.button, this.props.classes.delete)}
className={clsx(
this.props.classes.button,
this.props.classes.delete
)}
onClick={() => this.props.removeStep(index)}
>
<FontAwesomeIcon icon={faTrash} size="xs"/>
<FontAwesomeIcon icon={faTrash} size="xs" />
</IconButton>
</Tooltip>
</div>
: null}
) : null}
</div>
<div style={{width: '100%', marginLeft: '54px'}}>
<div style={{ width: "100%", marginLeft: "54px" }}>
<StepType value={this.props.step.type} index={index} />
<Textfield value={this.props.step.headline} property={'headline'} label={'Überschrift'} index={index} error={this.props.error.steps[index].headline} errorText={`Gib eine Überschrift für die ${this.props.step.type === 'task' ? 'Aufgabe' : 'Anleitung'} ein.`} />
<Textfield value={this.props.step.text} property={'text'} label={this.props.step.type === 'task' ? 'Aufgabenstellung' : 'Instruktionen'} index={index} multiline error={this.props.error.steps[index].text} errorText={`Gib Instruktionen für die ${this.props.step.type === 'task' ? 'Aufgabe' : 'Anleitung'} ein.`}/>
{index === 0 ?
<Textfield
value={this.props.step.headline}
property={"headline"}
label={"Überschrift"}
index={index}
error={this.props.error.steps[index].headline}
errorText={`Gib eine Überschrift für die ${
this.props.step.type === "task" ? "Aufgabe" : "Anleitung"
} ein.`}
/>
<MarkdownEditor
value={this.props.step.text}
property={"text"}
label={
this.props.step.type === "task"
? "Aufgabenstellung"
: "Instruktionen"
}
index={index}
multiline
error={this.props.error.steps[index].text}
errorText={`Gib Instruktionen für die ${
this.props.step.type === "task" ? "Aufgabe" : "Anleitung"
} ein.`}
/>
{index === 0 ? (
<div>
<Requirements value={this.props.step.requirements ? this.props.step.requirements : []} index={index}/>
<Hardware value={this.props.step.hardware ? this.props.step.hardware : []} index={index} error={this.props.error.steps[index].hardware}/>
<Requirements
value={
this.props.step.requirements
? this.props.step.requirements
: []
}
index={index}
/>
<Hardware
value={
this.props.step.hardware ? this.props.step.hardware : []
}
index={index}
error={this.props.error.steps[index].hardware}
/>
</div>
: null}
{this.props.step.type === 'instruction' ?
<Media value={this.props.step.media} picture={this.props.step.media && this.props.step.media.picture} youtube={this.props.step.media && this.props.step.media.youtube} url={this.props.step.url} index={index} error={this.props.error.steps[index].media} />
: null}
<BlocklyExample value={this.props.step.xml} index={index} task={this.props.step.type === 'task'} error={this.props.error.steps[index].xml ? true : false}/>
) : null}
<BlocklyExample
value={this.props.step.xml}
index={index}
task={this.props.step.type === "task"}
error={this.props.error.steps[index].xml ? true : false}
/>
</div>
</div>
</div>
);
};
}
}
Step.propTypes = {
@ -124,10 +200,14 @@ Step.propTypes = {
error: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
steps: state.builder.steps,
change: state.builder.change,
error: state.builder.error
error: state.builder.error,
});
export default connect(mapStateToProps, { addStep, removeStep, changeStepIndex })(withStyles(styles, {withTheme: true})(Step));
export default connect(mapStateToProps, {
addStep,
removeStep,
changeStepIndex,
})(withStyles(styles, { withTheme: true })(Step));

View File

@ -31,14 +31,6 @@ const styles = (theme) => ({
});
class Textfield extends Component {
componentDidMount() {
if (this.props.error) {
if (this.props.property !== "media") {
this.props.deleteError(this.props.index, this.props.property);
}
}
}
handleChange = (e) => {
var value = e.target.value;
if (this.props.property === "title") {

View File

@ -1,47 +1,45 @@
import React, { Component } from 'react';
import React, { Component } from "react";
import Dialog from '../Dialog';
import Dialog from "../Dialog";
import hardware from '../../data/hardware.json';
import hardware from "../../data/hardware.json";
import { withStyles } from '@material-ui/core/styles';
import withWidth, { isWidthDown } from '@material-ui/core/withWidth';
import Link from '@material-ui/core/Link';
import Typography from '@material-ui/core/Typography';
import IconButton from '@material-ui/core/IconButton';
import GridList from '@material-ui/core/GridList';
import GridListTile from '@material-ui/core/GridListTile';
import GridListTileBar from '@material-ui/core/GridListTileBar';
import { withStyles } from "@material-ui/core/styles";
import withWidth, { isWidthDown } from "@material-ui/core/withWidth";
import Link from "@material-ui/core/Link";
import Typography from "@material-ui/core/Typography";
import IconButton from "@material-ui/core/IconButton";
import ImageList from "@material-ui/core/ImageList";
import ImageListItem from "@material-ui/core/ImageListItem";
import ImageListItemBar from "@material-ui/core/ImageListItemBar";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faExpandAlt } from "@fortawesome/free-solid-svg-icons";
import * as Blockly from 'blockly'
const styles = theme => ({
import * as Blockly from "blockly";
const styles = (theme) => ({
expand: {
'&:hover': {
"&:hover": {
color: theme.palette.primary.main,
},
'&:active': {
"&:active": {
color: theme.palette.primary.main,
},
color: theme.palette.text.primary
color: theme.palette.text.primary,
},
multiGridListTile: {
multiImageListItem: {
background: theme.palette.primary.main,
opacity: 0.9,
height: '30px'
height: "30px",
},
multiImageListItemTitle: {
color: theme.palette.text.primary,
},
multiGridListTileTitle: {
color: theme.palette.text.primary
}
});
class Hardware extends Component {
state = {
open: false,
hardwareInfo: {}
hardwareInfo: {},
};
handleClickOpen = (hardwareInfo) => {
@ -53,36 +51,57 @@ class Hardware extends Component {
};
render() {
var cols = isWidthDown('md', this.props.width) ? isWidthDown('sm', this.props.width) ? isWidthDown('xs', this.props.width) ? 2 : 3 : 4 : 6;
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>{Blockly.Msg.tutorials_hardware_head}</Typography>
<GridList cellHeight={100} cols={cols} spacing={10}>
<ImageList rowHeight={100} cols={cols} gap={10}>
{this.props.picture.map((picture, i) => {
var hardwareInfo = hardware.filter(hardware => hardware.id === picture)[0];
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)} />
<ImageListItem 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 }}
<ImageListItemBar
classes={{ root: this.props.classes.multiImageListItem }}
title={
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }} className={this.props.classes.multiGridListTileTitle}>
<div
style={{ overflow: "hidden", textOverflow: "ellipsis" }}
className={this.props.classes.multiImageListItemTitle}
>
{hardwareInfo.name}
</div>
}
actionIcon={
<IconButton className={this.props.classes.expand} aria-label='Vollbild' onClick={() => this.handleClickOpen(hardwareInfo)}>
<IconButton
className={this.props.classes.expand}
aria-label="Vollbild"
onClick={() => this.handleClickOpen(hardwareInfo)}
>
<FontAwesomeIcon icon={faExpandAlt} size="xs" />
</IconButton>
}
/>
</GridListTile>
)
</ImageListItem>
);
})}
</GridList>
</ImageList>
<Dialog
style={{ zIndex: 1500 }}
@ -94,14 +113,26 @@ class Hardware extends Component {
button={Blockly.Msg.button_close}
>
<div>
<img src={`/media/hardware/${this.state.hardwareInfo.src}`} width="100%" alt={this.state.hardwareInfo.name} />
{Blockly.Msg.tutorials_hardware_moreInformation} <Link rel="noreferrer" target="_blank" href={this.state.hardwareInfo.url} color="primary">{Blockly.Msg.tutorials_hardware_here}</Link>.
<img
src={`/media/hardware/${this.state.hardwareInfo.src}`}
width="100%"
alt={this.state.hardwareInfo.name}
/>
{Blockly.Msg.tutorials_hardware_moreInformation}{" "}
<Link
rel="noreferrer"
target="_blank"
href={this.state.hardwareInfo.url}
color="primary"
>
{Blockly.Msg.tutorials_hardware_here}
</Link>
.
</div>
</Dialog>
</div>
);
};
}
}
export default withWidth()(withStyles(styles, { withTheme: true })(Hardware));

View File

@ -1,75 +1,79 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import Dialog from '../Dialog';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Dialog from "../Dialog";
import { withStyles } from '@material-ui/core/styles';
import Checkbox from '@material-ui/core/Checkbox';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import * as Blockly from 'blockly'
import ReactMarkdown from 'react-markdown';
import { withStyles } from "@material-ui/core/styles";
import Checkbox from "@material-ui/core/Checkbox";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import * as Blockly from "blockly";
import ReactMarkdown from "react-markdown";
const styles = (theme) => ({
link: {
color: theme.palette.primary.main,
textDecoration: 'none',
'&:hover': {
textDecoration: "none",
"&:hover": {
color: theme.palette.primary.main,
textDecoration: `underline`
}
textDecoration: `underline`,
},
},
label: {
fontSize: '0.9rem',
color: 'grey'
}
fontSize: "0.9rem",
color: "grey",
},
});
class HintTutorialExists extends Component {
constructor(props) {
var previousPageWasAnotherDomain = props.pageVisits === 0;
var userDoNotWantToSeeNews = window.localStorage.getItem('news') ? true : false;
var userDoNotWantToSeeNews = window.localStorage.getItem("news")
? true
: false;
super(props);
this.state = {
open: userDoNotWantToSeeNews ? !userDoNotWantToSeeNews : previousPageWasAnotherDomain
open: userDoNotWantToSeeNews
? !userDoNotWantToSeeNews
: previousPageWasAnotherDomain,
};
}
toggleDialog = () => {
this.setState({ open: !this.state });
}
};
onChange = (e) => {
if (e.target.checked) {
window.localStorage.setItem('news', e.target.checked);
window.localStorage.setItem("news", e.target.checked);
} else {
window.localStorage.removeItem("news");
}
else {
window.localStorage.removeItem('news');
}
}
};
render() {
return (
<Dialog
style={{ zIndex: 9999999 }}
fullWidth
maxWidth={'sm'}
maxWidth={"sm"}
open={this.state.open}
title={Blockly.Msg.messages_newblockly_head}
content={''}
content={""}
onClose={this.toggleDialog}
onClick={this.toggleDialog}
button={Blockly.Msg.button_close}
>
<div>
<ReactMarkdown linkTarget="_blank">{Blockly.Msg.messages_newblockly_text}</ReactMarkdown>
<ReactMarkdown linkTarget="_blank">
{Blockly.Msg.messages_newblockly_text}
</ReactMarkdown>
</div>
<FormControlLabel
style={{ marginTop: '20px' }}
style={{ marginTop: "20px" }}
classes={{ label: this.props.classes.label }}
control={
<Checkbox
size={'small'}
size={"small"}
value={true}
checked={this.state.checked}
onChange={(e) => this.onChange(e)}
@ -81,15 +85,18 @@ class HintTutorialExists extends Component {
/>
</Dialog>
);
};
}
}
HintTutorialExists.propTypes = {
pageVisits: PropTypes.number.isRequired
pageVisits: PropTypes.number.isRequired,
};
const mapStateToProps = state => ({
pageVisits: state.general.pageVisits
const mapStateToProps = (state) => ({
pageVisits: state.general.pageVisits,
});
export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(HintTutorialExists));
export default connect(
mapStateToProps,
null
)(withStyles(styles, { withTheme: true })(HintTutorialExists));

View File

@ -7,6 +7,8 @@ import BlocklyWindow from "../Blockly/BlocklyWindow";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import remarkGemoji from "remark-gemoji";
class Instruction extends Component {
render() {
@ -15,14 +17,13 @@ class Instruction extends Component {
var areRequirements = step.requirements && step.requirements.length > 0;
return (
<div>
<Typography variant="h4" style={{ marginBottom: "5px" }}>
{step.headline}
</Typography>
<Typography style={isHardware ? {} : { marginBottom: "5px" }}>
<ReactMarkdown
className={"tutorial"}
linkTarget={"_blank"}
skipHtml={false}
allowDangerousHtml={true}
remarkPlugins={[remarkGfm, remarkGemoji]}
>
{step.text}
</ReactMarkdown>

View File

@ -1,124 +1,233 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import clsx from 'clsx';
import { withRouter, Link } from 'react-router-dom';
import { fade } from '@material-ui/core/styles/colorManipulator';
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import List from '@material-ui/core/List';
import Tooltip from '@material-ui/core/Tooltip';
import clsx from "clsx";
import { withRouter, Link } from "react-router-dom";
import { alpha } from "@material-ui/core/styles";
import { withStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import List from "@material-ui/core/List";
import Tooltip from "@material-ui/core/Tooltip";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
import * as Blockly from 'blockly'
import * as Blockly from "blockly";
const styles = theme => ({
const styles = (theme) => ({
outerDiv: {
width: '50px',
height: '50px',
position: 'absolute',
color: fade(theme.palette.secondary.main, 0.6)
width: "50px",
height: "50px",
position: "absolute",
color: alpha(theme.palette.secondary.main, 0.6),
},
outerDivError: {
stroke: fade(theme.palette.error.dark, 0.6),
color: fade(theme.palette.error.dark, 0.6)
stroke: alpha(theme.palette.error.dark, 0.6),
color: alpha(theme.palette.error.dark, 0.6),
},
outerDivSuccess: {
stroke: fade(theme.palette.primary.main, 0.6),
color: fade(theme.palette.primary.main, 0.6)
stroke: alpha(theme.palette.primary.main, 0.6),
color: alpha(theme.palette.primary.main, 0.6),
},
outerDivOther: {
stroke: fade(theme.palette.secondary.main, 0.6)
stroke: alpha(theme.palette.secondary.main, 0.6),
},
innerDiv: {
width: 'inherit',
height: 'inherit',
display: 'table-cell',
verticalAlign: 'middle',
textAlign: 'center'
width: "inherit",
height: "inherit",
display: "table-cell",
verticalAlign: "middle",
textAlign: "center",
},
link: {
color: theme.palette.text.primary,
position: 'relative',
height: '50px',
display: 'flex',
margin: '5px 0 5px 10px',
textDecoration: 'none'
position: "relative",
height: "50px",
display: "flex",
margin: "5px 0 5px 10px",
textDecoration: "none",
},
hoverLink: {
'&:hover': {
background: fade(theme.palette.secondary.main, 0.5),
borderRadius: '0 25px 25px 0 '
}
}
"&:hover": {
background: alpha(theme.palette.secondary.main, 0.5),
borderRadius: "0 25px 25px 0 ",
},
},
});
class Requirement extends Component {
render() {
var requirements = this.props.requirements;
var tutorialIds = requirements.map(requirement => requirement._id);
var tutorialIds = requirements.map((requirement) => requirement._id);
return (
<div style={{ marginTop: '20px', marginBottom: '5px' }}>
<div style={{ marginTop: "20px", marginBottom: "5px" }}>
<Typography>{Blockly.Msg.tutorials_requirements}</Typography>
<List component="div">
{tutorialIds.map((tutorialId, i) => {
var title = requirements[i].title
var status = this.props.status.filter(status => status._id === tutorialId)[0];
var title = requirements[i].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 tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other';
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 (
<Link to={`/tutorial/${tutorialId}`} className={this.props.classes.link} key={i}>
<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>
<Link
target={"_blank"}
to={`/tutorial/${tutorialId}`}
className={this.props.classes.link}
key={i}
>
<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%' }}>
{error || success === 1 ?
<circle className={error ? this.props.classes.outerDivError : this.props.classes.outerDivSuccess} r="22.5" cx="50%" cy="50%" fill="none" strokeWidth="5"></circle>
: <circle className={this.props.classes.outerDivOther} r="22.5" cx="50%" cy="50%" fill="none" strokeWidth="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" strokeWidth="5" strokeDashoffset={`${(22.5 * 2 * Math.PI) * (1 - success)}`} strokeDasharray={`${(22.5 * 2 * Math.PI)}`}>
</circle>
: null}
<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"
strokeWidth="5"
></circle>
) : (
<circle
className={this.props.classes.outerDivOther}
r="22.5"
cx="50%"
cy="50%"
fill="none"
strokeWidth="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"
strokeWidth="5"
strokeDashoffset={`${
22.5 * 2 * Math.PI * (1 - success)
}`}
strokeDasharray={`${22.5 * 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={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>
}
{error || success === 1 ? (
<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>
);
};
}
}
Requirement.propTypes = {
status: PropTypes.array.isRequired,
change: PropTypes.number.isRequired
change: PropTypes.number.isRequired,
};
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
change: state.tutorial.change,
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)));

View File

@ -1,51 +1,49 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { tutorialCheck, tutorialStep } from '../../actions/tutorialActions';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { tutorialCheck, tutorialStep } from "../../actions/tutorialActions";
import { withRouter } from 'react-router-dom';
import { withRouter } from "react-router-dom";
import Compile from '../Workspace/Compile';
import Dialog from '../Dialog';
import Compile from "../Workspace/Compile";
import Dialog from "../Dialog";
import { checkXml } from '../../helpers/compareXml';
import { checkXml } from "../../helpers/compareXml";
import { withStyles } from '@material-ui/core/styles';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import Button from '@material-ui/core/Button';
import { withStyles } from "@material-ui/core/styles";
import IconButton from "@material-ui/core/IconButton";
import Tooltip from "@material-ui/core/Tooltip";
import Button from "@material-ui/core/Button";
import { faClipboardCheck } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as Blockly from 'blockly'
import * as Blockly from "blockly";
const styles = (theme) => ({
compile: {
backgroundColor: theme.palette.button.compile,
color: theme.palette.primary.contrastText,
'&:hover': {
"&:hover": {
backgroundColor: theme.palette.button.compile,
color: theme.palette.primary.contrastText,
}
}
},
},
});
class SolutionCheck extends Component {
state = {
open: false,
msg: ''
}
msg: "",
};
toggleDialog = () => {
if (this.state.open) {
this.setState({ open: false, msg: '' });
}
else {
this.setState({ open: false, msg: "" });
} else {
this.setState({ open: !this.state });
}
}
};
check = () => {
const tutorial = this.props.tutorial;
@ -53,7 +51,7 @@ class SolutionCheck extends Component {
var msg = checkXml(step.xml, this.props.xml);
this.props.tutorialCheck(msg.type, step);
this.setState({ msg, open: true });
}
};
render() {
const steps = this.props.tutorial.steps;
@ -62,68 +60,74 @@ class SolutionCheck extends Component {
<Tooltip title={Blockly.Msg.tooltip_check_solution} arrow>
<IconButton
className={`solutionCheck ${this.props.classes.compile}`}
style={{ width: '40px', height: '40px', marginRight: '5px' }}
style={{ width: "40px", height: "40px", marginRight: "5px" }}
onClick={() => this.check()}
>
<FontAwesomeIcon icon={faClipboardCheck} size="l" />
<FontAwesomeIcon icon={faClipboardCheck} size="xs" />
</IconButton>
</Tooltip>
<Dialog
style={{ zIndex: 9999999 }}
fullWidth
maxWidth={'sm'}
maxWidth={"sm"}
open={this.state.open}
title={this.state.msg.type === 'error' ? 'Fehler' : 'Erfolg'}
title={this.state.msg.type === "error" ? "Fehler" : "Erfolg"}
content={this.state.msg.text}
onClose={this.toggleDialog}
onClick={this.toggleDialog}
button={Blockly.Msg.button_close}
>
{this.state.msg.type === 'success' ?
<div style={{ marginTop: '20px', display: 'flex' }}>
{this.state.msg.type === "success" ? (
<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/`);
}}
>
{Blockly.Msg.button_tutorial_overview}
</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);
}}
>
{Blockly.Msg.button_next}
</Button>
}
)}
</div>
: null}
) : null}
</Dialog>
</div>
);
};
}
}
SolutionCheck.propTypes = {
tutorialCheck: PropTypes.func.isRequired,
tutorialStep: PropTypes.func.isRequired,
activeStep: PropTypes.number.isRequired,
xml: PropTypes.string.isRequired,
tutorial: PropTypes.object.isRequired
tutorial: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
activeStep: state.tutorial.activeStep,
xml: state.workspace.code.xml,
tutorial: state.tutorial.tutorials[0]
tutorial: state.tutorial.tutorials[0],
});
export default connect(mapStateToProps, { tutorialCheck, tutorialStep })(withStyles(styles, { withTheme: true })(withRouter(SolutionCheck)));
export default connect(mapStateToProps, { tutorialCheck, tutorialStep })(
withStyles(styles, { withTheme: true })(withRouter(SolutionCheck))
);

View File

@ -1,109 +1,183 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { withRouter } from 'react-router-dom';
import { withRouter } from "react-router-dom";
import clsx from 'clsx';
import clsx from "clsx";
// import tutorials from '../../data/tutorials';
import { alpha } from "@material-ui/core/styles";
import { fade } from '@material-ui/core/styles/colorManipulator';
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import Tooltip from '@material-ui/core/Tooltip';
import Button from '@material-ui/core/Button';
import { withStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import Tooltip from "@material-ui/core/Tooltip";
import Button from "@material-ui/core/Button";
import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const styles = (theme) => ({
stepper: {
width: 'calc(100% - 40px)',
height: '40px',
borderRadius: '25px',
padding: '0 20px',
margin: '20px 0',
display: 'flex',
justifyContent: 'space-between'
width: "calc(100% - 40px)",
height: "40px",
borderRadius: "25px",
padding: "0 20px",
margin: "20px 0",
display: "flex",
justifyContent: "space-between",
},
stepperSuccess: {
backgroundColor: fade(theme.palette.primary.main, 0.6),
backgroundColor: alpha(theme.palette.primary.main, 0.6),
},
stepperError: {
backgroundColor: fade(theme.palette.error.dark, 0.6),
backgroundColor: alpha(theme.palette.error.dark, 0.6),
},
stepperOther: {
backgroundColor: fade(theme.palette.secondary.main, 0.6),
backgroundColor: alpha(theme.palette.secondary.main, 0.6),
},
color: {
backgroundColor: 'transparent '
backgroundColor: "transparent ",
},
iconDivSuccess: {
color: theme.palette.primary.main
color: theme.palette.primary.main,
},
iconDivError: {
color: theme.palette.error.dark
}
color: theme.palette.error.dark,
},
});
class StepperHorizontal extends Component {
render() {
var tutorialId = this.props.tutorial._id;
var status = this.props.status.filter(status => status._id === tutorialId)[0];
var status = this.props.status.filter(
(status) => status._id === tutorialId
)[0];
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';
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";
var title = this.props.tutorial.title;
var activeStep = this.props.activeStep;
return (
<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 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
disabled//={tutorialIndex === 0}
//onClick={() => { this.props.history.push(`/tutorial/${tutorials[tutorialIndex - 1].id}`) }}
disabled //={tutorialIndex === 0}
//onClick={() => { this.props.history.push(`/tutorial/${tutorials[tutorialIndex - 1].id}`) }}
>
{'<'}
{"<"}
</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}
{title !== this.props.tutorial.steps[activeStep].headline
? ` - ${this.props.tutorial.steps[activeStep].headline}`
: null}
</Typography>
</div>
</Tooltip>
<Button
disabled//={tutorialIndex + 1 === tutorials.length}
//onClick={() => { this.props.history.push(`/tutorial/${tutorials[tutorialIndex + 1].id}`) }}
disabled //={tutorialIndex + 1 === tutorials.length}
//onClick={() => { this.props.history.push(`/tutorial/${tutorials[tutorialIndex + 1].id}`) }}
>
{'>'}
{">"}
</Button>
</div>
</div>
);
};
}
}
StepperHorizontal.propTypes = {
status: PropTypes.array.isRequired,
change: PropTypes.number.isRequired,
currentTutorialIndex: PropTypes.number.isRequired,
tutorial: PropTypes.object.isRequired
tutorial: PropTypes.object.isRequired,
activeStep: PropTypes.number.isRequired,
};
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
change: state.tutorial.change,
status: state.tutorial.status,
currentTutorialIndex: state.tutorial.currentIndex,
tutorial: state.tutorial.tutorials[0]
activeStep: state.tutorial.activeStep,
tutorial: state.tutorial.tutorials[0],
});
export default connect(mapStateToProps, null)(withRouter(withStyles(styles, { withTheme: true })(StepperHorizontal)));
export default connect(
mapStateToProps,
null
)(withRouter(withStyles(styles, { withTheme: true })(StepperHorizontal)));

View File

@ -1,36 +1,35 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { tutorialStep } from '../../actions/tutorialActions';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { tutorialStep } from "../../actions/tutorialActions";
import { withRouter } from 'react-router-dom';
import { withRouter } from "react-router-dom";
import clsx from 'clsx';
import { fade } from '@material-ui/core/styles/colorManipulator';
import { withStyles } from '@material-ui/core/styles';
import Stepper from '@material-ui/core/Stepper';
import Step from '@material-ui/core/Step';
import StepLabel from '@material-ui/core/StepLabel';
import Tooltip from '@material-ui/core/Tooltip';
import clsx from "clsx";
import { alpha } from "@material-ui/core/styles";
import { withStyles } from "@material-ui/core/styles";
import Stepper from "@material-ui/core/Stepper";
import Step from "@material-ui/core/Step";
import StepLabel from "@material-ui/core/StepLabel";
import Tooltip from "@material-ui/core/Tooltip";
const styles = (theme) => ({
verticalStepper: {
padding: 0,
width: '30px',
width: "30px",
},
stepIcon: {
borderStyle: `solid`,
// borderWidth: '2px',
borderRadius: '50%',
borderRadius: "50%",
borderColor: theme.palette.secondary.main,
width: '12px',
height: '12px',
margin: '0 auto',
width: "12px",
height: "12px",
margin: "0 auto",
},
stepIconLarge: {
width: '24px',
height: '24px'
width: "24px",
height: "24px",
},
stepIconLargeSuccess: {
borderColor: theme.palette.primary.main,
@ -39,28 +38,27 @@ const styles = (theme) => ({
borderColor: theme.palette.error.dark,
},
stepIconActiveOther: {
backgroundColor: theme.palette.secondary.main
backgroundColor: theme.palette.secondary.main,
},
stepIconActiveSuccess: {
backgroundColor: fade(theme.palette.primary.main, 0.6)
backgroundColor: alpha(theme.palette.primary.main, 0.6),
},
stepIconActiveError: {
backgroundColor: fade(theme.palette.error.dark, 0.6)
backgroundColor: alpha(theme.palette.error.dark, 0.6),
},
connector: {
height: '10px',
height: "10px",
borderLeft: `2px solid black`,
margin: 'auto'
}
margin: "auto",
},
});
class StepperVertical extends Component {
componentDidMount(){
componentDidMount() {
this.props.tutorialStep(0);
}
componentDidUpdate(props){
componentDidUpdate(props) {
if (props.tutorial._id !== this.props.match.params.tutorialId) {
this.props.tutorialStep(0);
}
@ -69,60 +67,103 @@ class StepperVertical extends Component {
render() {
var steps = this.props.steps;
var activeStep = this.props.activeStep;
var tutorialStatus = this.props.status.filter(status => status._id === this.props.tutorial._id)[0];
var tutorialStatus = this.props.status.filter(
(status) => status._id === this.props.tutorial._id
)[0];
return (
<div style={{marginRight: '10px'}}>
<div style={{ marginRight: "10px" }}>
<Stepper
activeStep={activeStep}
orientation="vertical"
connector={<div className={this.props.classes.connector}></div>}
classes={{root: this.props.classes.verticalStepper}}
classes={{ root: this.props.classes.verticalStepper }}
>
{steps.map((step, i) => {
var tasksIndex = tutorialStatus.tasks.findIndex(task => task._id === step._id);
var taskType = tasksIndex > -1 ? tutorialStatus.tasks[tasksIndex].type : null;
var taskStatus = taskType === 'success' ? 'Success' : taskType === 'error' ? 'Error' : 'Other';
var tasksIndex = tutorialStatus.tasks.findIndex(
(task) => task._id === step._id
);
var taskType =
tasksIndex > -1 ? tutorialStatus.tasks[tasksIndex].type : null;
var taskStatus =
taskType === "success"
? "Success"
: taskType === "error"
? "Error"
: "Other";
return (
<Step key={i}>
<Tooltip title={step.headline} placement='right' arrow >
<div style={i === activeStep ? {padding: '5px 0'} : {padding: '5px 0', cursor: 'pointer'}} onClick={i === activeStep ? null : () => { this.props.tutorialStep(i)}}>
<Tooltip title={step.headline} placement="right" arrow>
<div
style={
i === activeStep
? { padding: "5px 0" }
: { padding: "5px 0", cursor: "pointer" }
}
onClick={
i === activeStep
? null
: () => {
this.props.tutorialStep(i);
}
}
>
<StepLabel
StepIconComponent={'div'}
StepIconComponent={"div"}
classes={{
root: step.type === 'task' ?
i === activeStep ?
clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIconLarge'+taskStatus], this.props.classes['stepIconActive'+taskStatus])
: clsx(this.props.classes.stepIcon, this.props.classes.stepIconLarge, this.props.classes['stepIconLarge'+taskStatus])
: i === activeStep ?
clsx(this.props.classes.stepIcon, this.props.classes.stepIconActiveOther)
: clsx(this.props.classes.stepIcon)
root:
step.type === "task"
? i === activeStep
? clsx(
this.props.classes.stepIcon,
this.props.classes.stepIconLarge,
this.props.classes[
"stepIconLarge" + taskStatus
],
this.props.classes[
"stepIconActive" + taskStatus
]
)
: clsx(
this.props.classes.stepIcon,
this.props.classes.stepIconLarge,
this.props.classes[
"stepIconLarge" + taskStatus
]
)
: i === activeStep
? clsx(
this.props.classes.stepIcon,
this.props.classes.stepIconActiveOther
)
: clsx(this.props.classes.stepIcon),
}}
>
</StepLabel>
></StepLabel>
</div>
</Tooltip>
</Step>
)})}
);
})}
</Stepper>
</div>
);
};
}
}
StepperVertical.propTypes = {
status: PropTypes.array.isRequired,
change: PropTypes.number.isRequired,
activeStep: PropTypes.number.isRequired,
tutorialStep: PropTypes.func.isRequired,
tutorial: PropTypes.object.isRequired
tutorial: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
change: state.tutorial.change,
status: state.tutorial.status,
activeStep: state.tutorial.activeStep,
tutorial: state.tutorial.tutorials[0]
tutorial: state.tutorial.tutorials[0],
});
export default connect(mapStateToProps, { tutorialStep })(withRouter(withStyles(styles, {withTheme: true})(StepperVertical)));
export default connect(mapStateToProps, { tutorialStep })(
withRouter(withStyles(styles, { withTheme: true })(StepperVertical))
);

View File

@ -3,9 +3,13 @@ import PropTypes from "prop-types";
import { connect } from "react-redux";
import {
getTutorials,
getAllTutorials,
getUserTutorials,
resetTutorial,
tutorialProgress,
} from "../../actions/tutorialActions";
import { progress } from "../../actions/tutorialBuilderActions";
import { clearMessages } from "../../actions/messageActions";
import clsx from "clsx";
@ -13,16 +17,26 @@ import clsx from "clsx";
import Breadcrumbs from "../Breadcrumbs";
import { Link } from "react-router-dom";
import { fade } from "@material-ui/core/styles/colorManipulator";
import { alpha } from "@material-ui/core/styles";
import { withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import Paper from "@material-ui/core/Paper";
import Typography from "@material-ui/core/Typography";
import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
import {
faCheck,
faTimes,
faShareAlt,
faEye,
faUserCheck,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as Blockly from "blockly";
import ReactStars from "react-rating-stars-component";
import Tooltip from "@material-ui/core/Tooltip";
import IconButton from "@material-ui/core/IconButton";
import Snackbar from "../Snackbar";
import Divider from "@material-ui/core/Divider";
const styles = (theme) => ({
outerDiv: {
@ -31,18 +45,18 @@ const styles = (theme) => ({
bottom: "-30px",
width: "160px",
height: "160px",
color: fade(theme.palette.secondary.main, 0.6),
color: alpha(theme.palette.secondary.main, 0.6),
},
outerDivError: {
stroke: fade(theme.palette.error.dark, 0.6),
color: fade(theme.palette.error.dark, 0.6),
stroke: alpha(theme.palette.error.dark, 0.6),
color: alpha(theme.palette.error.dark, 0.6),
},
outerDivSuccess: {
stroke: fade(theme.palette.primary.main, 0.6),
color: fade(theme.palette.primary.main, 0.6),
stroke: alpha(theme.palette.primary.main, 0.6),
color: alpha(theme.palette.primary.main, 0.6),
},
outerDivOther: {
stroke: fade(theme.palette.secondary.main, 0.6),
stroke: alpha(theme.palette.secondary.main, 0.6),
},
innerDiv: {
width: "inherit",
@ -51,23 +65,85 @@ const styles = (theme) => ({
verticalAlign: "middle",
textAlign: "center",
},
button: {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
width: "40px",
height: "40px",
"&:hover": {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
},
},
link: {
color: theme.palette.primary.main,
textDecoration: "none",
"&:hover": {
color: theme.palette.primary.main,
textDecoration: "underline",
},
},
});
class TutorialHome extends Component {
constructor(props) {
super(props);
this.state = {
userTutorials: [],
tutorials: [],
snackbar: false,
};
}
componentDidMount() {
this.props.tutorialProgress();
// retrieve tutorials only if a potential user is loaded - authentication
// is finished (success or failed)
if (!this.props.progress) {
this.props.getTutorials();
// if (!this.props.progress) {
// if (this.props.user) {
// if (this.props.user.blocklyRole === "admin") {
// this.props.getAllTutorials();
// }
// if (this.props.user.blocklyRole === "creator") {
// this.props.getUserTutorials();
// this.props.getTutorials();
// console.log("get user tutorials");
// console.log(this.props.userTutorials);
// }
// } else {
// this.props.getTutorials();
// }
// }
if (!this.props.authProgress) {
if (this.props.user) {
if (this.props.user.role === "admin") {
this.props.getAllTutorials();
} else {
this.props.getUserTutorials();
//this.props.getTutorials();
}
} else {
this.props.getTutorials();
}
}
}
componentDidUpdate(props, state) {
if (props.progress !== this.props.progress && !this.props.progress) {
// authentication is completed
this.props.getTutorials();
}
if (
props.authProgress !== this.props.authProgress &&
!this.props.authProgress
)
if (this.props.user) {
if (this.props.user.role === "admin") {
// authentication is completed
this.props.getAllTutorials();
} else {
this.props.getUserTutorials();
}
} else {
this.props.getTutorials();
}
if (this.props.message.id === "GET_TUTORIALS_FAIL") {
alert(this.props.message.msg);
}
@ -81,13 +157,25 @@ class TutorialHome extends Component {
}
render() {
var userTutorials = [];
const publicTutorials = this.props.tutorials.filter(
(tutorial) => tutorial.public === true
);
if (this.props.user && this.props.user.blocklyRole === "admin") {
userTutorials = this.props.tutorials;
}
if (this.props.user && this.props.user.blocklyRole === "creator") {
userTutorials = this.props.tutorials.filter(
(tutorial) => tutorial.creator === this.props.user.email
);
}
return this.props.isLoading ? null : (
<div>
<Breadcrumbs content={[{ link: "/tutorial", title: "Tutorial" }]} />
<h1>{Blockly.Msg.tutorials_home_head}</h1>
<h2>Alle Tutorials</h2>
<Grid container spacing={2}>
{this.props.tutorials.map((tutorial, i) => {
{publicTutorials.map((tutorial, i) => {
var status = this.props.status.filter(
(status) => status._id === tutorial._id
)[0];
@ -99,6 +187,12 @@ class TutorialHome extends Component {
tasks.length;
var tutorialStatus =
success === 1 ? "Success" : error ? "Error" : "Other";
const firstExample = {
size: 30,
value: tutorial.difficulty,
edit: false,
isHalf: true,
};
return (
<Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}>
<Link
@ -114,6 +208,7 @@ class TutorialHome extends Component {
}}
>
{tutorial.title}
<ReactStars {...firstExample} />
<div
className={clsx(this.props.classes.outerDiv)}
style={{ width: "160px", height: "160px", border: 0 }}
@ -216,6 +311,119 @@ class TutorialHome extends Component {
);
})}
</Grid>
{this.props.user ? (
<div>
<h2>User Tutorials</h2>
<Grid container spacing={2}>
{userTutorials.map((tutorial, i) => {
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 tutorialStatus =
success === 1 ? "Success" : error ? "Error" : "Other";
const firstExample = {
size: 30,
value: tutorial.difficulty,
edit: false,
isHalf: true,
};
return (
<Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}>
<Paper
style={{
height: "150",
padding: "10px",
position: "relative",
overflow: "hidden",
backgroundColor: tutorial.review
? "lightyellow"
: "white",
}}
>
<Link
to={`/tutorial/${tutorial._id}`}
style={{ textDecoration: "none", color: "inherit" }}
>
{tutorial.title}
<ReactStars {...firstExample} />
</Link>
<Divider
style={{
marginTop: "10px",
marginBottom: "10px",
}}
/>
<p>
Creator:{tutorial.creator} <br />
<div style={this.props.style}>
<Tooltip
title={Blockly.Msg.tooltip_share_tutorial}
arrow
>
<IconButton
className={`shareTutorial ${this.props.classes.button}`}
onClick={() => {
navigator.clipboard.writeText(
`${window.location.origin}/tutorial/${tutorial._id}`
);
this.setState({
snackbar: true,
key: Date.now(),
message:
Blockly.Msg.messages_copylink_success,
type: "success",
});
}}
>
<FontAwesomeIcon icon={faShareAlt} size="xs" />
</IconButton>
</Tooltip>
<Tooltip
title={Blockly.Msg.tooltip_share_tutorial}
arrow
>
<IconButton
className={`publicTutorial ${this.props.classes.button}`}
disabled={!tutorial.public}
>
<FontAwesomeIcon icon={faEye} size="xs" />
</IconButton>
</Tooltip>
{tutorial.review ? (
<Tooltip
title={Blockly.Msg.tooltip_share_tutorial}
arrow
>
<IconButton
className={`publicTutorial ${this.props.classes.button}`}
disabled={!tutorial.review}
>
<FontAwesomeIcon icon={faUserCheck} size="xs" />
</IconButton>
</Tooltip>
) : null}
<Snackbar
open={this.state.snackbar}
message={Blockly.Msg.messages_copylink_success}
type={this.state.type}
key={this.state.key}
/>
</div>
</p>
</Paper>
</Grid>
);
})}
</Grid>
</div>
) : null}
</div>
);
}
@ -223,6 +431,8 @@ class TutorialHome extends Component {
TutorialHome.propTypes = {
getTutorials: PropTypes.func.isRequired,
getAllTutorials: PropTypes.func.isRequired,
getUserTutorials: PropTypes.func.isRequired,
resetTutorial: PropTypes.func.isRequired,
tutorialProgress: PropTypes.func.isRequired,
clearMessages: PropTypes.func.isRequired,
@ -232,19 +442,27 @@ TutorialHome.propTypes = {
isLoading: PropTypes.bool.isRequired,
message: PropTypes.object.isRequired,
progress: PropTypes.bool.isRequired,
user: PropTypes.object.isRequired,
authProgress: PropTypes.bool.isRequired,
};
const mapStateToProps = (state) => ({
change: state.tutorial.change,
status: state.tutorial.status,
tutorials: state.tutorial.tutorials,
userTutorials: state.tutorial.userTutorials,
isLoading: state.tutorial.progress,
message: state.message,
progress: state.auth.progress,
user: state.auth.user,
authProgress: state.auth.progress,
});
export default connect(mapStateToProps, {
getTutorials,
progress,
getUserTutorials,
getAllTutorials,
resetTutorial,
clearMessages,
tutorialProgress,

View File

@ -50,7 +50,6 @@ export class Login extends Component {
}
// Check for login error
else if (message.id === "LOGIN_FAIL") {
console.log("login fail");
this.setState({
email: "",
password: "",

View File

@ -0,0 +1,71 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { workspaceName } from "../../actions/workspaceActions";
import SaveIcon from "../CodeEditor/SaveIcon";
const resetTimeout = (id, newID) => {
clearTimeout(id);
return newID;
};
class AutoSave extends Component {
constructor(props) {
super(props);
this.state = {
timeout: null,
value: "",
saved: false,
autosave: false,
};
}
editValue = (value) => {
this.setState({
timeout: resetTimeout(
this.state.timeout,
setTimeout(this.saveValue, 400)
),
value: value,
});
};
saveValue = () => {
this.setState({ ...this.state, saved: true });
localStorage.setItem("autoSaveXML", this.props.xml);
setTimeout(() => this.setState({ ...this.state, saved: false }), 1000);
};
componentDidMount() {
}
componentDidUpdate(prevProps) {
if (prevProps.xml !== this.props.xml) {
this.editValue(this.props.xml);
}
}
render() {
return (
<div>
<SaveIcon loading={this.state.saved} autosave={this.props.autosave} />
</div>
);
}
}
AutoSave.propTypes = {
xml: PropTypes.string.isRequired,
name: PropTypes.string,
workspaceName: PropTypes.func.isRequired,
setAutosave: PropTypes.func.isRequired,
autosave: PropTypes.bool.isRequired,
};
const mapStateToProps = (state) => ({
auto: state.general.autosave,
xml: state.workspace.code.xml,
name: state.workspace.name,
});
export default connect(mapStateToProps, { workspaceName })(AutoSave);

View File

@ -16,10 +16,7 @@ import { faClipboardCheck } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as Blockly from "blockly/core";
import Copy from "../copy.svg";
import Prism from "prismjs";
import "prismjs/themes/prism.css";
import "prismjs/plugins/line-numbers/prism-line-numbers";
import "prismjs/plugins/line-numbers/prism-line-numbers.css";
import MuiDrawer from "@material-ui/core/Drawer";
import Dialog from "../Dialog";
@ -71,15 +68,12 @@ class Compile extends Component {
};
}
componentDidMount() {
Prism.highlightAll();
}
componentDidMount() {}
componentDidUpdate(props) {
if (props.name !== this.props.name) {
this.setState({ name: this.props.name });
}
Prism.highlightAll();
}
compile = () => {
@ -95,7 +89,6 @@ class Compile extends Component {
})
.then((response) => response.json())
.then((data) => {
console.log(data);
if (data.code === "Internal Server Error") {
this.setState({
progress: false,
@ -196,7 +189,7 @@ class Compile extends Component {
className={`compileBlocks ${this.props.classes.iconButton}`}
onClick={() => this.compile()}
>
<FontAwesomeIcon icon={faClipboardCheck} size="l" />
<FontAwesomeIcon icon={faClipboardCheck} size="xs" />
</IconButton>
</Tooltip>
) : (

View File

@ -1,99 +1,110 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { workspaceName } from '../../actions/workspaceActions';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { workspaceName } from "../../actions/workspaceActions";
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import { withStyles } from "@material-ui/core/styles";
import Button from "@material-ui/core/Button";
import IconButton from "@material-ui/core/IconButton";
import Tooltip from "@material-ui/core/Tooltip";
import { faCopy } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as Blockly from 'blockly/core';
import Snackbar from '../Snackbar';
import * as Blockly from "blockly/core";
import Snackbar from "../Snackbar";
const styles = (theme) => ({
backdrop: {
zIndex: theme.zIndex.drawer + 1,
color: '#fff',
backdrop: {
zIndex: theme.zIndex.drawer + 1,
color: "#fff",
},
iconButton: {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
width: "40px",
height: "40px",
"&:hover": {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
},
iconButton: {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
width: '40px',
height: '40px',
'&:hover': {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
}
},
button: {
backgroundColor: theme.palette.button.copycode,
color: theme.palette.primary.contrastText,
"&:hover": {
backgroundColor: theme.palette.button.copycode,
color: theme.palette.primary.contrastText,
},
button: {
backgroundColor: theme.palette.button.copycode,
color: theme.palette.primary.contrastText,
'&:hover': {
backgroundColor: theme.palette.button.copycode,
color: theme.palette.primary.contrastText,
}
}
},
});
class CopyCode extends Component {
constructor(props) {
super(props);
this.state = {
snackbar: false,
};
}
copyCode = () => {
navigator.clipboard.writeText(this.props.arduino)
this.setState({ snackbar: true, type: 'success', key: Date.now(), message: Blockly.Msg.messages_copy_code });
}
render() {
return (
<div style={{}}>
{this.props.iconButton ?
<Tooltip title={Blockly.Msg.tooltip_copy_code} arrow style={{ marginRight: '5px' }}>
<IconButton
className={`copyCode ${this.props.classes.iconButton}`}
onClick={() => this.copyCode()}
>
<FontAwesomeIcon icon={faCopy} size="l" />
</IconButton>
</Tooltip>
:
<Button style={{ float: 'right', color: 'white' }} variant="contained" className={this.props.classes.button} onClick={() => this.copyCode()}>
<FontAwesomeIcon icon={faCopy} style={{ marginRight: '5px' }} /> Code kopieren
</Button>
}
<Snackbar
open={this.state.snackbar}
message={this.state.message}
type={this.state.type}
key={this.state.key}
/>
</div >
);
constructor(props) {
super(props);
this.state = {
snackbar: false,
};
}
copyCode = () => {
navigator.clipboard.writeText(this.props.arduino);
this.setState({
snackbar: true,
type: "success",
key: Date.now(),
message: Blockly.Msg.messages_copy_code,
});
};
render() {
return (
<div style={{}}>
{this.props.iconButton ? (
<Tooltip
title={Blockly.Msg.tooltip_copy_code}
arrow
style={{ marginRight: "5px" }}
>
<IconButton
className={`copyCode ${this.props.classes.iconButton}`}
onClick={() => this.copyCode()}
>
<FontAwesomeIcon icon={faCopy} size="xs" />
</IconButton>
</Tooltip>
) : (
<Button
style={{ float: "right", color: "white" }}
variant="contained"
className={this.props.classes.button}
onClick={() => this.copyCode()}
>
<FontAwesomeIcon icon={faCopy} style={{ marginRight: "5px" }} />{" "}
Code kopieren
</Button>
)}
<Snackbar
open={this.state.snackbar}
message={this.state.message}
type={this.state.type}
key={this.state.key}
/>
</div>
);
}
}
CopyCode.propTypes = {
arduino: PropTypes.string.isRequired,
name: PropTypes.string,
workspaceName: PropTypes.func.isRequired
arduino: PropTypes.string.isRequired,
name: PropTypes.string,
workspaceName: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
arduino: state.workspace.code.arduino,
name: state.workspace.name
const mapStateToProps = (state) => ({
arduino: state.workspace.code.arduino,
name: state.workspace.name,
});
export default connect(mapStateToProps, { workspaceName })(withStyles(styles, { withTheme: true })(CopyCode));
export default connect(mapStateToProps, { workspaceName })(
withStyles(styles, { withTheme: true })(CopyCode)
);

View File

@ -1,16 +1,16 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import * as Blockly from 'blockly/core';
import * as Blockly from "blockly/core";
import { saveAs } from 'file-saver';
import { saveAs } from "file-saver";
import { detectWhitespacesAndReturnReadableResult } from '../../helpers/whitespace';
import { detectWhitespacesAndReturnReadableResult } from "../../helpers/whitespace";
import { withStyles } from '@material-ui/core/styles';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import { withStyles } from "@material-ui/core/styles";
import IconButton from "@material-ui/core/IconButton";
import Tooltip from "@material-ui/core/Tooltip";
import { faCamera } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -19,18 +19,16 @@ const styles = (theme) => ({
button: {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
width: '40px',
height: '40px',
'&:hover': {
width: "40px",
height: "40px",
"&:hover": {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
}
}
},
},
});
class Screenshot extends Component {
getSvg = () => {
const workspace = Blockly.getMainWorkspace();
var canvas = workspace.svgBlockCanvas_.cloneNode(true);
@ -39,10 +37,12 @@ class Screenshot extends Component {
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 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, ".");
}
}
// ensure that fill-opacity is 1, because there cannot be a replacing
@ -56,19 +56,24 @@ class Screenshot extends Component {
.blocklyPathLight {
display: flex;
} `;
var css = '<defs><style type="text/css" xmlns="http://www.w3.org/1999/xhtml"><![CDATA[' + cssContent + ']]></style></defs>';
var bbox = document.getElementsByClassName("blocklyBlockCanvas")[0].getBBox();
var css =
'<defs><style type="text/css" xmlns="http://www.w3.org/1999/xhtml"><![CDATA[' +
cssContent +
"]]></style></defs>";
var bbox = document
.getElementsByClassName("blocklyBlockCanvas")[0]
.getBBox();
var content = new XMLSerializer().serializeToString(canvas);
var xml = `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="${bbox.width}" height="${bbox.height}" viewBox="${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}">
${css}">${content}</svg>`;
var fileName = detectWhitespacesAndReturnReadableResult(this.props.name);
// this.props.workspaceName(this.state.name);
fileName = `${fileName}.svg`
var blob = new Blob([xml], { type: 'image/svg+xml;base64' });
fileName = `${fileName}.svg`;
var blob = new Blob([xml], { type: "image/svg+xml;base64" });
saveAs(blob, fileName);
}
}
};
render() {
return (
@ -83,15 +88,18 @@ class Screenshot extends Component {
</Tooltip>
</div>
);
};
}
}
Screenshot.propTypes = {
name: PropTypes.string.isRequired,
name: PropTypes.string,
};
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
name: state.workspace.name,
});
export default connect(mapStateToProps, null)(withStyles(styles, { withTheme: true })(Screenshot));
export default connect(
mapStateToProps,
null
)(withStyles(styles, { withTheme: true })(Screenshot));

View File

@ -1,72 +1,106 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { shareProject } from '../../actions/projectActions';
import { clearMessages } from '../../actions/messageActions';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { shareProject } from "../../actions/projectActions";
import { clearMessages } from "../../actions/messageActions";
import QRCode from 'qrcode.react';
import { createId } from "mnemonic-id";
import moment from 'moment';
import moment from "moment";
import Dialog from '../Dialog';
import Snackbar from '../Snackbar';
import Dialog from "../Dialog";
import Snackbar from "../Snackbar";
import { Link } from 'react-router-dom';
// import { Link } from "react-router-dom";
import { withStyles } from '@material-ui/core/styles';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import Typography from '@material-ui/core/Typography';
import GridLoader from "react-spinners/GridLoader";
import { EmailShareButton, FacebookShareButton, TwitterShareButton, WhatsappShareButton} from "react-share";
import { EmailIcon, FacebookIcon, TwitterIcon, WhatsappIcon} from "react-share";
import { withStyles } from "@material-ui/core/styles";
import IconButton from "@material-ui/core/IconButton";
import Button from '@material-ui/core/Button';
import Tooltip from "@material-ui/core/Tooltip";
import Typography from "@material-ui/core/Typography";
import { faShareAlt, faCopy } from "@fortawesome/free-solid-svg-icons";
import { faShareAlt, faCopy, faDownload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as Blockly from 'blockly/core';
import * as Blockly from "blockly/core";
const styles = (theme) => ({
iconButton: {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
width: "40px",
height: "40px",
"&:hover": {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
},
},
button: {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
width: '40px',
height: '40px',
'&:hover': {
"&:hover": {
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
}
},
borderRadius: 20,
},
link: {
color: theme.palette.primary.main,
textDecoration: 'none',
'&:hover': {
textDecoration: "none",
"&:hover": {
color: theme.palette.primary.main,
textDecoration: 'underline'
}
}
textDecoration: "underline",
},
},
});
class WorkspaceFunc extends Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
this.state = {
snackbar: false,
type: '',
key: '',
message: '',
title: '',
content: '',
type: "",
key: "",
message: "",
title: "",
content: "",
open: false,
id: '',
id: "",
shortLink: "",
isFetching: false,
loading: false,
};
}
componentDidUpdate(props) {
if (this.props.message !== props.message) {
if (this.props.message.id === 'SHARE_SUCCESS' && (!this.props.multiple || this.props.message.status === this.props.project._id)) {
this.setState({ share: true, open: true, title: Blockly.Msg.messages_SHARE_SUCCESS, id: this.props.message.status });
}
else if (this.props.message.id === 'SHARE_FAIL' && (!this.props.multiple || this.props.message.status === this.props.project._id)) {
this.setState({ snackbar: true, key: Date.now(), message: Blockly.Msg.messages_SHARE_FAIL, type: 'error' });
if (
this.props.message.id === "SHARE_SUCCESS" &&
(!this.props.multiple ||
this.props.message.status === this.props.project._id)
) {
this.createShortlink(this.props.message.status);
this.setState({
share: true,
open: true,
title: Blockly.Msg.messages_SHARE_SUCCESS,
id: this.props.message.status,
});
} else if (
this.props.message.id === "SHARE_FAIL" &&
(!this.props.multiple ||
this.props.message.status === this.props.project._id)
) {
this.setState({
snackbar: true,
key: Date.now(),
message: Blockly.Msg.messages_SHARE_FAIL,
type: "error",
});
window.scrollTo(0, 0);
}
}
@ -77,25 +111,58 @@ class WorkspaceFunc extends Component {
}
toggleDialog = () => {
this.setState({ open: !this.state, title: '', content: '' });
}
this.setState({ open: !this.state, title: "", content: "" });
};
shareBlocks = () => {
if (this.props.projectType === 'project' && this.props.project.shared) {
if (this.props.projectType === "project" && this.props.project.shared) {
// project is already shared
this.setState({ open: true, title: Blockly.Msg.messages_SHARE_SUCCESS, id: this.props.project._id });
}
else {
this.props.shareProject(this.props.name || this.props.project.title, this.props.projectType, this.props.project ? this.props.project._id : undefined);
this.setState({
open: true,
title: Blockly.Msg.messages_SHARE_SUCCESS,
id: this.props.project._id,
});
} else {
this.props.shareProject(
this.props.name || this.props.project.title,
this.props.projectType,
this.props.project ? this.props.project._id : undefined
);
}
};
createShortlink(id) {
this.setState({ isFetching: true, loading: true })
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ "slug": `blockly-${createId(5)}`, "url": `${window.location.origin}/share/${id}` })
};
fetch('https://www.snsbx.de/api/shorty', requestOptions)
.then(response => response.json())
.then(data => this.setState({ shortLink: data[0].link, isFetching: false, loading: false }));
}
downloadQRCode = () => {
// Generate download with use canvas and stream
const canvas = document.getElementById("qr-gen");
const pngUrl = canvas
.toDataURL("image/png")
.replace("image/png", "image/octet-stream");
let downloadLink = document.createElement("a");
downloadLink.href = pngUrl;
downloadLink.download = `${this.state.shortLink}.png`;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
};
render() {
return (
<div style={this.props.style}>
<Tooltip title={Blockly.Msg.tooltip_share_project} arrow>
<IconButton
className={`shareBlocks ${this.props.classes.button}`}
className={`shareBlocks ${this.props.classes.iconButton}`}
onClick={() => this.shareBlocks()}
>
<FontAwesomeIcon icon={faShareAlt} size="xs" />
@ -115,44 +182,134 @@ class WorkspaceFunc extends Component {
onClose={this.toggleDialog}
onClick={this.toggleDialog}
button={Blockly.Msg.button_close}
>
<div style={{ marginTop: '10px' }}>
<Typography>Über den folgenden Link kannst du dein Programm teilen:</Typography>
<Link to={`/share/${this.state.id}`} onClick={() => this.toggleDialog()} className={this.props.classes.link}>{`${window.location.origin}/share/${this.state.id}`}</Link>
<Tooltip title={Blockly.Msg.tooltip_copy_link} arrow style={{ marginRight: '5px' }}>
<IconButton
onClick={() => {
navigator.clipboard.writeText(`${window.location.origin}/share/${this.state.id}`);
this.setState({ snackbar: true, key: Date.now(), message: Blockly.Msg.messages_copylink_success, type: 'success' });
}}
>
<FontAwesomeIcon icon={faCopy} size="xs" />
</IconButton>
</Tooltip>
{this.props.project && this.props.project.shared && this.props.message.id !== 'SHARE_SUCCESS' ?
<Typography variant='body2' style={{ marginTop: '20px' }}>{`Das Projekt wurde bereits geteilt. Der Link ist noch mindestens ${moment(this.props.project.shared).diff(moment().utc(), 'days') === 0 ?
moment(this.props.project.shared).diff(moment().utc(), 'hours') === 0 ?
`${moment(this.props.project.shared).diff(moment().utc(), 'minutes')} Minuten`
: `${moment(this.props.project.shared).diff(moment().utc(), 'hours')} Stunden`
: `${moment(this.props.project.shared).diff(moment().utc(), 'days')} Tage`} gültig.`}</Typography>
: <Typography variant='body2' style={{ marginTop: '20px' }}>{`Der Link ist nun ${process.env.REACT_APP_SHARE_LINK_EXPIRES} Tage gültig.`}</Typography>}
</div>
>
{ this.state.isFetching ? (
<div style={{ display: 'flex', justifyContent: 'center'}}>
<GridLoader color={"#4EAF47"} loading={this.state.loading} size={50} />
</div>
) : (
<div style={{ marginTop: "10px" }}>
<Typography>
Über den folgenden Link kannst du dein Programm teilen:
</Typography>
<div style={{ textAlign: "center" }}>
<a
href={this.state.shortLink}
onClick={() => this.toggleDialog()}
className={this.props.classes.link}
target="_blank"
rel="noreferrer"
>{this.state.shortLink}</a>
<Tooltip
title={Blockly.Msg.tooltip_copy_link}
arrow
style={{ marginRight: "5px" }}
>
<IconButton
onClick={() => {
navigator.clipboard.writeText(
this.state.shortLink
);
this.setState({
snackbar: true,
key: Date.now(),
message: Blockly.Msg.messages_copylink_success,
type: "success",
});
}}
>
<FontAwesomeIcon icon={faCopy} size="xs" />
</IconButton>
</Tooltip>
</div>
<div style={{ marginTop: "10px", display: 'flex', justifyContent: 'center' }}>
<QRCode
id="qr-gen"
value={this.state.shortLink}
size={256}
level={"L"}
includeMargin={false}
/>
</div>
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '20px'}}>
<Button className={`download QR Code ${this.props.classes.button}`} onClick={() => this.downloadQRCode()} variant="contained" startIcon={<FontAwesomeIcon icon={faDownload} size="xs" />}>
Download QR code
</Button>
</div>
<div style={{ display: 'flex', justifyContent: 'center', marginTop: "20px"}}>
<FacebookShareButton url={this.state.shortLink} quote={"I created this sketch for my senseBox. Have a look!"} hashtag={"#senseBox"}>
<FacebookIcon size={32} round />
</FacebookShareButton>
<TwitterShareButton url={this.state.shortLink} title={"I created this sketch for my senseBox. Have a look!"} hashtags={["senseBox", "Blockly", "citizenScience"]}>
<TwitterIcon size={32} round />
</TwitterShareButton>
<WhatsappShareButton url={this.state.shortLink} title={"Look at my SenseBox sketch that I created with Blockly!"} separator={": "}>
<WhatsappIcon size={32} round />
</WhatsappShareButton>
<EmailShareButton url={this.state.shortLink} subject={"SenseBox Blockly Sketch"} body={"I created this sketch for my senseBox. Have a look!"} separator={": "}>
<EmailIcon size={32} round />
</EmailShareButton>
</div>
{ this.props.project &&
this.props.project.shared &&
this.props.message.id !== "SHARE_SUCCESS" ? (
<Typography
variant="body2"
style={{ marginTop: "20px" }}
>
{`Das Projekt wurde bereits geteilt. Der Link ist noch mindestens ${
moment(this.props.project.shared).diff(
moment().utc(),
"days"
) === 0
? moment(this.props.project.shared).diff(
moment().utc(),
"hours"
) === 0
? `${moment(this.props.project.shared).diff(
moment().utc(),
"minutes"
)} Minuten`
: `${moment(this.props.project.shared).diff(
moment().utc(),
"hours"
)} Stunden`
: `${moment(this.props.project.shared).diff(
moment().utc(),
"days"
)} Tage`
} gültig.`}
</Typography>
) : (
<Typography
variant="body2"
style={{ marginTop: "20px" }}
>
{`Der Link ist nun ${process.env.REACT_APP_SHARE_LINK_EXPIRES} Tage gültig.`}
</Typography>
)
}
</div>
)
}
</Dialog>
</div>
);
};
}
}
WorkspaceFunc.propTypes = {
shareProject: PropTypes.func.isRequired,
clearMessages: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
message: PropTypes.object.isRequired
message: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
name: state.workspace.name,
message: state.message
message: state.message,
});
export default connect(mapStateToProps, { shareProject, clearMessages })(withStyles(styles, { withTheme: true })(WorkspaceFunc));
export default connect(mapStateToProps, { shareProject, clearMessages })(
withStyles(styles, { withTheme: true })(WorkspaceFunc)
);

View File

@ -1,101 +1,110 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import WorkspaceName from './WorkspaceName';
import SaveProject from './SaveProject';
import Compile from './Compile';
import SolutionCheck from '../Tutorial/SolutionCheck';
import DownloadProject from './DownloadProject';
import OpenProject from './OpenProject';
import Screenshot from './Screenshot';
import ShareProject from './ShareProject';
import ResetWorkspace from './ResetWorkspace';
import DeleteProject from './DeleteProject';
import CopyCode from './CopyCode';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import WorkspaceName from "./WorkspaceName";
import SaveProject from "./SaveProject";
import Compile from "./Compile";
import SolutionCheck from "../Tutorial/SolutionCheck";
import DownloadProject from "./DownloadProject";
import OpenProject from "./OpenProject";
import Screenshot from "./Screenshot";
import ShareProject from "./ShareProject";
import ResetWorkspace from "./ResetWorkspace";
import DeleteProject from "./DeleteProject";
import CopyCode from "./CopyCode";
import AutoSave from "./AutoSave";
class WorkspaceFunc extends Component {
render() {
return (
<div style={{ width: 'max-content', display: 'flex' }}>
{!this.props.assessment ?
<div
style={{ width: "max-content", display: "flex", alignItems: "center" }}
>
{!this.props.assessment & !this.props.multiple ? <AutoSave /> : null}
{!this.props.assessment ? (
<WorkspaceName
style={{ marginRight: '5px' }}
style={{ marginRight: "5px" }}
multiple={this.props.multiple}
project={this.props.project}
projectType={this.props.projectType}
/>
: null}
) : null}
{this.props.assessment ?
{this.props.assessment ? (
<SolutionCheck />
: !this.props.multiple ?
<Compile iconButton />
: null}
) : !this.props.multiple ? (
<Compile iconButton />
) : null}
{!this.props.multiple ?
<CopyCode iconButton />
: null}
{!this.props.multiple ? <CopyCode iconButton /> : null}
{this.props.user && !this.props.multiple ?
{this.props.user && !this.props.multiple ? (
<SaveProject
style={{ marginRight: '5px' }}
style={{ marginRight: "5px" }}
projectType={this.props.projectType}
project={this.props.project}
/>
: null}
) : null}
{!this.props.multiple ?
<DownloadProject style={{ marginRight: '5px' }} />
: null}
{!this.props.multiple ? (
<DownloadProject style={{ marginRight: "5px" }} />
) : null}
{!this.props.assessment && !this.props.multiple ?
{!this.props.assessment && !this.props.multiple ? (
<OpenProject
style={{ marginRight: '5px' }}
style={{ marginRight: "5px" }}
assessment={this.props.assessment}
/>
: null}
) : null}
{!this.props.assessment && !this.props.multiple ?
<Screenshot style={{ marginRight: '5px' }} />
: null}
{!this.props.assessment && !this.props.multiple ? (
<Screenshot style={{ marginRight: "5px" }} />
) : null}
{this.props.projectType !== 'gallery' && !this.props.assessment ?
{this.props.projectType !== "gallery" && !this.props.assessment ? (
<ShareProject
style={{ marginRight: '5px' }}
style={{ marginRight: "5px" }}
multiple={this.props.multiple}
project={this.props.project}
projectType={this.props.projectType}
/>
: null}
) : null}
{!this.props.multiple ?
<ResetWorkspace style={this.props.projectType === 'project' || this.props.projectType === 'gallery' ? { marginRight: '5px' } : null}
{!this.props.multiple ? (
<ResetWorkspace
style={
this.props.projectType === "project" ||
this.props.projectType === "gallery"
? { marginRight: "5px" }
: null
}
/>
: null}
) : null}
{!this.props.assessment && (this.props.projectType === 'project' || this.props.projectType === 'gallery') && this.props.user && this.props.user.email === this.props.project.creator ?
{!this.props.assessment &&
(this.props.projectType === "project" ||
this.props.projectType === "gallery") &&
this.props.user &&
this.props.user.email === this.props.project.creator ? (
<DeleteProject
project={this.props.project}
projectType={this.props.projectType}
/>
: null}
) : null}
</div>
);
};
}
}
WorkspaceFunc.propTypes = {
user: PropTypes.object
user: PropTypes.object,
autosave: PropTypes.bool.isRequired,
};
const mapStateToProps = state => ({
user: state.auth.user
const mapStateToProps = (state) => ({
user: state.auth.user,
autosave: state.workspace.autosave,
});
export default connect(mapStateToProps, null)(WorkspaceFunc);

View File

@ -1,51 +1,50 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { workspaceName } from '../../actions/workspaceActions';
import { setDescription, updateProject } from '../../actions/projectActions';
import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { workspaceName } from "../../actions/workspaceActions";
import { setDescription, updateProject } from "../../actions/projectActions";
import Snackbar from '../Snackbar';
import Dialog from '../Dialog';
import Snackbar from "../Snackbar";
import Dialog from "../Dialog";
import withWidth, { isWidthDown } from '@material-ui/core/withWidth';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import Tooltip from '@material-ui/core/Tooltip';
import TextField from '@material-ui/core/TextField';
import Typography from '@material-ui/core/Typography';
import withWidth, { isWidthDown } from "@material-ui/core/withWidth";
import { withStyles } from "@material-ui/core/styles";
import Button from "@material-ui/core/Button";
import Tooltip from "@material-ui/core/Tooltip";
import TextField from "@material-ui/core/TextField";
import Typography from "@material-ui/core/Typography";
import { faPen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as Blockly from 'blockly/core'
import * as Blockly from "blockly/core";
const styles = (theme) => ({
workspaceName: {
minHeight: "40px",
backgroundColor: theme.palette.secondary.main,
borderRadius: '25px',
display: 'inline-flex',
cursor: 'pointer',
'&:hover': {
borderRadius: "25px",
display: "inline-flex",
cursor: "pointer",
"&:hover": {
color: theme.palette.primary.main,
}
}
},
},
});
class WorkspaceName extends Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
this.state = {
title: '',
content: '',
title: "",
content: "",
open: false,
name: props.name,
description: props.description,
snackbar: false,
type: '',
key: '',
message: ''
type: "",
key: "",
message: "",
};
}
@ -59,47 +58,100 @@ class WorkspaceName extends Component {
}
toggleDialog = () => {
this.setState({ open: !this.state, title: '', content: '' });
}
this.setState({ open: !this.state, title: "", content: "" });
};
setFileName = (e) => {
this.setState({ name: e.target.value });
}
};
setDescription = (e) => {
this.setState({ description: e.target.value });
}
};
renameWorkspace = () => {
this.props.workspaceName(this.state.name);
this.toggleDialog();
if (this.props.projectType === 'project' || this.props.projectType === 'gallery' || this.state.projectType === 'gallery') {
if (this.props.projectType === 'gallery' || this.state.projectType === 'gallery') {
if (
this.props.projectType === "project" ||
this.props.projectType === "gallery" ||
this.state.projectType === "gallery"
) {
if (
this.props.projectType === "gallery" ||
this.state.projectType === "gallery"
) {
this.props.setDescription(this.state.description);
}
if (this.state.projectType === 'gallery') {
if (this.state.projectType === "gallery") {
this.saveGallery();
} else {
this.props.updateProject(this.props.projectType, this.props.project._id);
this.props.updateProject(
this.props.projectType,
this.props.project._id
);
}
} else {
this.setState({ snackbar: true, type: 'success', key: Date.now(), message: `${Blockly.Msg.messages_rename_success_01} ${this.state.name} ${Blockly.Msg.messages_rename_success_02}` });
this.setState({
snackbar: true,
type: "success",
key: Date.now(),
message: `${Blockly.Msg.messages_rename_success_01} ${this.state.name} ${Blockly.Msg.messages_rename_success_02}`,
});
}
}
};
render() {
return (
<div style={this.props.style}>
<Tooltip title={`${Blockly.Msg.tooltip_project_title} ${this.props.name ? `: ${this.props.name}` : ''}`} arrow style={{ height: '100%' }}>
<Tooltip
title={`${Blockly.Msg.tooltip_project_title} ${
this.props.name ? `: ${this.props.name}` : ""
}`}
arrow
style={{ height: "100%" }}
>
<div
className={this.props.classes.workspaceName}
onClick={() => { if (this.props.multiple) { this.props.workspaceName(this.props.project.title); if (this.props.projectType === 'gallery') { this.props.setDescription(this.props.project.description); } } this.setState({ open: true, title: this.props.projectType === 'gallery' ? 'Projektdaten ändern' : this.props.projectType === 'project' ? 'Projekt umbenennen' : 'Projekt benennen', content: this.props.projectType === 'gallery' ? 'Bitte gib einen Titel und eine Beschreibung für das Galerie-Projekt ein und bestätige die Angaben mit einem Klick auf \'Eingabe\'.' : 'Bitte gib einen Namen für das Projekt ein und bestätige diesen mit einem Klick auf \'Eingabe\'.' }) }}
onClick={() => {
if (this.props.multiple) {
this.props.workspaceName(this.props.project.title);
if (this.props.projectType === "gallery") {
this.props.setDescription(this.props.project.description);
}
}
this.setState({
open: true,
title:
this.props.projectType === "gallery"
? "Projektdaten ändern"
: this.props.projectType === "project"
? "Projekt umbenennen"
: "Projekt benennen",
content:
this.props.projectType === "gallery"
? "Bitte gib einen Titel und eine Beschreibung für das Galerie-Projekt ein und bestätige die Angaben mit einem Klick auf 'Eingabe'."
: "Bitte gib einen Namen für das Projekt ein und bestätige diesen mit einem Klick auf 'Eingabe'.",
});
}}
>
{this.props.name && !isWidthDown(this.props.projectType === 'project' || this.props.projectType === 'gallery' ? 'xl' : 'xs', this.props.width) ?
<Typography style={{ margin: 'auto -3px auto 12px' }}>{this.props.name}</Typography>
: null}
<div style={{ width: '40px', display: 'flex' }}>
<FontAwesomeIcon icon={faPen} style={{ height: '18px', width: '18px', margin: 'auto' }} />
{this.props.name &&
!isWidthDown(
this.props.projectType === "project" ||
this.props.projectType === "gallery"
? "xl"
: "xs",
this.props.width
) ? (
<Typography style={{ margin: "auto -3px auto 12px" }}>
{this.props.name}
</Typography>
) : null}
<div style={{ width: "40px", display: "flex" }}>
<FontAwesomeIcon
icon={faPen}
style={{ height: "18px", width: "18px", margin: "auto" }}
/>
</div>
</div>
</Tooltip>
@ -114,38 +166,88 @@ class WorkspaceName extends Component {
open={this.state.open}
title={this.state.title}
content={this.state.content}
onClose={() => { this.toggleDialog(); this.setState({ name: this.props.name, description: this.props.description }); }}
onClick={() => { this.toggleDialog(); this.setState({ name: this.props.name, description: this.props.description }); }}
button={'Abbrechen'}
onClose={() => {
this.toggleDialog();
this.setState({
name: this.props.name,
description: this.props.description,
});
}}
onClick={() => {
this.toggleDialog();
this.setState({
name: this.props.name,
description: this.props.description,
});
}}
button={"Abbrechen"}
>
<div style={{ marginTop: '10px' }}>
{this.props.projectType === 'gallery' || this.state.projectType === 'gallery' ?
<div style={{ marginTop: "10px" }}>
{this.props.projectType === "gallery" ||
this.state.projectType === "gallery" ? (
<div>
<TextField autoFocus placeholder={this.state.saveXml ? 'Dateiname' : 'Projekttitel'} value={this.state.name} onChange={this.setFileName} style={{ marginBottom: '10px' }} />
<TextField fullWidth multiline placeholder={'Projektbeschreibung'} value={this.state.description} onChange={this.setDescription} style={{ marginBottom: '10px' }} />
<TextField
autoFocus
placeholder={
this.state.saveXml ? "Dateiname" : "Projekttitel"
}
value={this.state.name}
onChange={this.setFileName}
style={{ marginBottom: "10px" }}
/>
<TextField
fullWidth
multiline
placeholder={"Projektbeschreibung"}
value={this.state.description}
onChange={this.setDescription}
style={{ marginBottom: "10px" }}
/>
</div>
: <TextField autoFocus placeholder={this.state.saveXml ? 'Dateiname' : 'Projekttitel'} value={this.state.name} onChange={this.setFileName} style={{ marginRight: '10px' }} />}
<Button disabled={!this.state.name} variant='contained' color='primary' onClick={() => { this.renameWorkspace(); this.toggleDialog(); }}>Eingabe</Button>
) : (
<TextField
autoFocus
placeholder={this.state.saveXml ? "Dateiname" : "Projekttitel"}
value={this.state.name}
onChange={this.setFileName}
style={{ marginRight: "10px" }}
/>
)}
<Button
disabled={!this.state.name}
variant="contained"
color="primary"
onClick={() => {
this.renameWorkspace();
this.toggleDialog();
}}
>
Eingabe
</Button>
</div>
</Dialog>
</div>
);
};
}
}
WorkspaceName.propTypes = {
workspaceName: PropTypes.func.isRequired,
setDescription: PropTypes.func.isRequired,
updateProject: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
name: PropTypes.string,
description: PropTypes.string.isRequired,
message: PropTypes.object.isRequired,
};
const mapStateToProps = state => ({
const mapStateToProps = (state) => ({
name: state.workspace.name,
description: state.project.description,
message: state.message,
});
export default connect(mapStateToProps, { workspaceName, setDescription, updateProject })(withStyles(styles, { withTheme: true })(withWidth()(WorkspaceName)));
export default connect(mapStateToProps, {
workspaceName,
setDescription,
updateProject,
})(withStyles(styles, { withTheme: true })(withWidth()(WorkspaceName)));

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 763 KiB

After

Width:  |  Height:  |  Size: 229 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

View File

@ -3,7 +3,8 @@
"id": "senseboxmcu",
"name": "senseBox MCU",
"src": "senseboxmcu.png",
"url": "https://docs.sensebox.de/hardware/allgemein-sensebox-mcu/"
"url": "https://docs.sensebox.de/hardware/allgemein-sensebox-mcu/",
"description": "test"
},
{
"id": "breadboard",

151
src/data/versions.js Normal file
View File

@ -0,0 +1,151 @@
export const LibraryVersions = () => {
return [
{
library: "SSD1306 Plot Library",
link: "https://github.com/sensebox/SSD1306-Plot-Library/",
},
{
library: "SDS011 Library",
link: "https://github.com/sensebox/SDS011-select-serial/",
},
{
library: "RV8523 Arduino Library",
link: "https://github.com/sensebox/RV8523-RTC-Arduino-Library",
},
{
library: "BMX055_Library",
link: "https://github.com/sensebox/BMX055-Arduino-Library/",
},
{
library: "LTR329",
link: "https://github.com/sensebox/LTR329-Lightsensor-Arduino-Library/",
},
{
library: "VEML6070",
link: "https://github.com/sensebox/VEML6070-UV-Arduino-Library/",
},
{
library: "senseBox Web Library",
link: "https://github.com/sensebox/sensebox-libweb/",
},
{
library: "Arduino WiFi101",
link: "https://github.com/arduino-libraries/WiFi101",
},
{
library: "Ethernet",
link: "https://github.com/arduino-libraries/Ethernet",
},
{
library: "ArduinoJson",
link: "https://github.com/bblanchon/ArduinoJson",
},
{
library: "Adafruit Sensor Library",
link: "https://github.com/adafruit/Adafruit_Sensor",
},
{
library: "Adafruit HDC1000 Library",
link: "https://github.com/adafruit/Adafruit_HDC1000_Library",
},
{
library: "Adafruit BME280 Library",
link: "https://github.com/adafruit/Adafruit_BME280_Library",
},
{
library: "Adafruit BMP280 Library",
link: "https://github.com/adafruit/Adafruit_BMP280_Library",
},
{
library: "Adafruit BME680 Library",
link: "https://github.com/adafruit/Adafruit_BME680",
},
{
library: "Adafruit DPS310",
link: "https://github.com/adafruit/Adafruit_DPS310",
},
{
library: "Adafruit NeoPixel",
link: "https://github.com/adafruit/Adafruit_NeoPixel",
},
{
library: "Adafruit SSD1306",
link: "https://github.com/adafruit/Adafruit_SSD1306",
},
{
library: "Adafruit GFX Library",
link: "https://github.com/adafruit/Adafruit-GFX-Library",
},
{
library: "Adafruit MQTT Library",
link: "https://github.com/adafruit/Adafruit_MQTT_Library",
},
{
library: "Adafruit BusIO",
link: "https://github.com/adafruit/Adafruit_BusIO",
},
{
library: "Adafruit SleepyDog Library",
link: "https://github.com/adafruit/Adafruit_SleepyDog",
},
{
library: "DallasTemperature",
link: "https://github.com/milesburton/Arduino-Temperature-Control-Library",
},
{
library: "ArduinoBearSSL",
link: "https://github.com/arduino-libraries/ArduinoBearSSL",
},
{
library: "ArduinoECCX08",
link: "https://github.com/arduino-libraries/ArduinoECCX08",
},
{
library: "SparkFun SCD30 Arduino Library",
link: "https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library",
},
{
library: "SparkFun u-blox GNSS Arduino Library",
link: "https://github.com/sparkfun/SparkFun_u-blox_GNSS_Arduino_Library",
},
{
library: "NewPing",
link: "https://bitbucket.org/teckel12/arduino-new-ping/wiki/Home",
},
{
library: "IBM LMIC framework",
link: "https://github.com/matthijskooijman/arduino-lmic",
},
{
library: "LoRa Serialization",
link: "https://github.com/thesolarnomad/lora-serialization",
},
{
library: "CayenneLPP",
link: "https://github.com/ElectronicCats/CayenneLPP",
},
{ library: "OneWire", link: "https://github.com/PaulStoffregen/OneWire" },
{
library: "Nova Fitness Sds dust sensors library",
link: "https://github.com/lewapek/sds-dust-sensors-arduino-library",
},
{ library: "JC_Button", link: "https://github.com/JChristensen/JC_Button" },
{ library: "SD", link: "https://github.com/arduino-libraries/SD" },
{
library: "BSEC Software Library",
link: "https://github.com/BoschSensortec/BSEC-Arduino-library",
},
{
library: "TheThingsNetwork",
link: "https://github.com/TheThingsNetwork/arduino-device-lib",
},
{
library: "NTPClient",
link: "https://github.com/arduino-libraries/NTPClient",
},
{
library: "phyphox BLE",
link: "https://github.com/phyphox/phyphox-arduino",
},
];
};

View File

@ -1,30 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import * as Sentry from "@sentry/react";
import { Integrations } from "@sentry/tracing";
Sentry.init({
dsn: "https://ffe5d54461f64c46b4bed5d77c130d6f@o507523.ingest.sentry.io/5598758",
autoSessionTracking: true,
integrations: [
new Integrations.BrowserTracing(),
],
// We recommend adjusting this value in production, or using tracesSampler
// for finer control
tracesSampleRate: 1.0,
});
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
document.getElementById("root")
);
// If you want your app to work offline and load faster, you can change

View File

@ -0,0 +1,24 @@
import { BOARD } from '../actions/types';
const initialValue = () => {
if (window.localStorage.getItem("board")) {
return window.localStorage.getItem("board");
}
return "bla";
};
const initialState = {
board: initialValue()
};
export default function foo(state = initialState, action){
switch(action.type){
case BOARD:
return {
...state,
board: action.payload,
};
default:
return state;
}
}

View File

@ -25,7 +25,7 @@ const initialSounds = () => {
if (window.localStorage.getItem("sounds")) {
return window.localStorage.getItem("sounds");
} else {
return "off";
return false;
}
};

View File

@ -6,9 +6,11 @@ import generalReducer from './generalReducer';
import projectReducer from './projectReducer';
import messageReducer from './messageReducer';
import authReducer from './authReducer';
import boardReducer from './boardReducer'
export default combineReducers({
auth: authReducer,
board: boardReducer,
workspace: workspaceReducer,
tutorial: tutorialReducer,
builder: tutorialBuilderReducer,

View File

@ -10,6 +10,9 @@ import {
BUILDER_CHANGE_STEP,
BUILDER_CHANGE_ORDER,
BUILDER_DELETE_PROPERTY,
BUILDER_DIFFICULTY,
BUILDER_PUBLIC,
BUILDER_REVIEW,
} from "../actions/types";
const initialState = {
@ -17,6 +20,9 @@ const initialState = {
progress: false,
json: "",
title: "",
difficulty: 0,
public: false,
review: false,
id: "",
steps: [
{
@ -45,6 +51,21 @@ export default function foo(state = initialState, action) {
...state,
title: action.payload,
};
case BUILDER_PUBLIC:
return {
...state,
public: action.payload,
};
case BUILDER_DIFFICULTY:
return {
...state,
difficulty: action.payload,
};
case BUILDER_REVIEW:
return {
...state,
review: action.payload,
};
case BUILDER_ID:
return {
...state,

View File

@ -1,24 +1,23 @@
import { TUTORIAL_PROGRESS, GET_TUTORIAL, GET_TUTORIALS, GET_STATUS, TUTORIAL_SUCCESS, TUTORIAL_ERROR, TUTORIAL_CHANGE, TUTORIAL_XML, TUTORIAL_STEP } from '../actions/types';
//
// const initialStatus = () => {
// if(store.getState().auth.user){
// return store.getState().auth.user.status || []
// }
// else if (window.localStorage.getItem('status')) {
// var status = JSON.parse(window.localStorage.getItem('status'));
// return status;
// }
// return [];
// };
import {
TUTORIAL_PROGRESS,
GET_TUTORIAL,
GET_TUTORIALS,
GET_USERTUTORIALS,
GET_STATUS,
TUTORIAL_SUCCESS,
TUTORIAL_ERROR,
TUTORIAL_CHANGE,
TUTORIAL_XML,
TUTORIAL_STEP,
} from "../actions/types";
const initialState = {
status: [],
activeStep: 0,
change: 0,
tutorials: [],
progress: false
userTutorials: [],
progress: false,
};
export default function foo(state = initialState, action) {
@ -26,18 +25,23 @@ export default function foo(state = initialState, action) {
case TUTORIAL_PROGRESS:
return {
...state,
progress: !state.progress
}
progress: !state.progress,
};
case GET_TUTORIALS:
return {
...state,
tutorials: action.payload
tutorials: action.payload,
};
case GET_USERTUTORIALS:
return {
...state,
tutorials: action.payload,
};
case GET_TUTORIAL:
return {
...state,
tutorials: [action.payload]
}
tutorials: [action.payload],
};
case TUTORIAL_SUCCESS:
case TUTORIAL_ERROR:
case TUTORIAL_XML:
@ -46,23 +50,23 @@ export default function foo(state = initialState, action) {
// and 'TUTORIAL_XML' the function 'updateStatus' is called
return {
...state,
status: action.payload
status: action.payload,
};
case GET_STATUS:
return {
...state,
status: action.payload
status: action.payload,
};
case TUTORIAL_CHANGE:
return {
...state,
change: state.change += 1
}
change: (state.change += 1),
};
case TUTORIAL_STEP:
return {
...state,
activeStep: action.payload
}
activeStep: action.payload,
};
default:
return state;
}

View File

@ -1,6 +1,6 @@
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import rootReducer from "./reducers";
const initialState = {};
@ -10,7 +10,7 @@ const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(...middleware),
applyMiddleware(...middleware)
// window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
);