diff --git a/.env b/.env index 9af8f29..044e061 100644 --- a/.env +++ b/.env @@ -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 diff --git a/.gitignore b/.gitignore index 273210c..f0c13ca 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,4 @@ npm-debug.log* yarn-debug.log* yarn-error.log* package-lock.json -package-lock.json + diff --git a/package.json b/package.json index 0a85d8e..418a0a5 100644 --- a/package.json +++ b/package.json @@ -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" + ] } diff --git a/public/media/hardware/senseboxmcumini.png b/public/media/hardware/senseboxmcumini.png new file mode 100644 index 0000000..220caa6 Binary files /dev/null and b/public/media/hardware/senseboxmcumini.png differ diff --git a/src/App.css b/src/App.css index 1a8a13d..8457170 100644 --- a/src/App.css +++ b/src/App.css @@ -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; +} diff --git a/src/App.js b/src/App.js index ef9ca04..29289e5 100644 --- a/src/App.js +++ b/src/App.js @@ -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()); } diff --git a/src/actions/authActions.js b/src/actions/authActions.js index 04a2265..086d703 100644 --- a/src/actions/authActions.js +++ b/src/actions/authActions.js @@ -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, diff --git a/src/actions/boardAction.js b/src/actions/boardAction.js new file mode 100644 index 0000000..f78cb82 --- /dev/null +++ b/src/actions/boardAction.js @@ -0,0 +1,10 @@ +import { + BOARD, + } from "./types"; + +export const setBoard = (board) => (dispatch) => { + dispatch({ + type: BOARD, + payload: board, + }); + }; \ No newline at end of file diff --git a/src/actions/tutorialActions.js b/src/actions/tutorialActions.js index 372697a..226bba6 100644 --- a/src/actions/tutorialActions.js +++ b/src/actions/tutorialActions.js @@ -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 diff --git a/src/actions/tutorialBuilderActions.js b/src/actions/tutorialBuilderActions.js index 72d7f3e..cb4e798 100644 --- a/src/actions/tutorialBuilderActions.js +++ b/src/actions/tutorialBuilderActions.js @@ -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)); diff --git a/src/actions/types.js b/src/actions/types.js index 6a10987..c67ad2a 100644 --- a/src/actions/types.js +++ b/src/actions/types.js @@ -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"; diff --git a/src/components/Alert.js b/src/components/Alert.js index ce10c87..01bac66 100644 --- a/src/components/Alert.js +++ b/src/components/Alert.js @@ -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 (
- - {this.props.children} - + {this.props.children}
); } } - export default withStyles(styles, { withTheme: true })(Alert); diff --git a/src/components/Blockly/BlocklyWindow.js b/src/components/Blockly/BlocklyWindow.js index c69fc9d..bbab82d 100644 --- a/src/components/Blockly/BlocklyWindow.js +++ b/src/components/Blockly/BlocklyWindow.js @@ -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 })( diff --git a/src/components/Blockly/blocks/sensebox-led.js b/src/components/Blockly/blocks/sensebox-led.js index 981133e..6810fcb 100644 --- a/src/components/Blockly/blocks/sensebox-led.js +++ b/src/components/Blockly/blocks/sensebox-led.js @@ -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') diff --git a/src/components/Blockly/blocks/sensebox-sensors.js b/src/components/Blockly/blocks/sensebox-sensors.js index 145a5e1..12929a7 100644 --- a/src/components/Blockly/blocks/sensebox-sensors.js +++ b/src/components/Blockly/blocks/sensebox-sensors.js @@ -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); diff --git a/src/components/Blockly/generator/generator.js b/src/components/Blockly/generator/generator.js index 9217522..599f022 100644 --- a/src/components/Blockly/generator/generator.js +++ b/src/components/Blockly/generator/generator.js @@ -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; }; /** diff --git a/src/components/Blockly/generator/sensebox-ble.js b/src/components/Blockly/generator/sensebox-ble.js index bd6ddd7..34a3a21 100644 --- a/src/components/Blockly/generator/sensebox-ble.js +++ b/src/components/Blockly/generator/sensebox-ble.js @@ -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 !== "") { diff --git a/src/components/Blockly/generator/sensebox-sd.js b/src/components/Blockly/generator/sensebox-sd.js index ab54e47..419b76a 100644 --- a/src/components/Blockly/generator/sensebox-sd.js +++ b/src/components/Blockly/generator/sensebox-sd.js @@ -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; }; diff --git a/src/components/Blockly/generator/sensebox-web.js b/src/components/Blockly/generator/sensebox-web.js index a2cae3b..3d94af1 100644 --- a/src/components/Blockly/generator/sensebox-web.js +++ b/src/components/Blockly/generator/sensebox-web.js @@ -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]; }; + + diff --git a/src/components/Blockly/generator/variables.js b/src/components/Blockly/generator/variables.js index 7ee7be9..e8e4dd4 100644 --- a/src/components/Blockly/generator/variables.js +++ b/src/components/Blockly/generator/variables.js @@ -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; }; diff --git a/src/components/Blockly/helpers/board.js b/src/components/Blockly/helpers/board.js index 16e0165..12dd18d 100644 --- a/src/components/Blockly/helpers/board.js +++ b/src/components/Blockly/helpers/board.js @@ -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; }; diff --git a/src/components/Blockly/msg/de/ui.js b/src/components/Blockly/msg/de/ui.js index f73db9f..e11f9f9 100644 --- a/src/components/Blockly/msg/de/ui.js +++ b/src/components/Blockly/msg/de/ui.js @@ -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" }; diff --git a/src/components/Blockly/msg/en/ui.js b/src/components/Blockly/msg/en/ui.js index 530fd90..6a1e685 100644 --- a/src/components/Blockly/msg/en/ui.js +++ b/src/components/Blockly/msg/en/ui.js @@ -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", + + }; diff --git a/src/components/Blockly/toolbox/Toolbox.js b/src/components/Blockly/toolbox/Toolbox.js index 00cc54d..7fa9f5a 100644 --- a/src/components/Blockly/toolbox/Toolbox.js +++ b/src/components/Blockly/toolbox/Toolbox.js @@ -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(); diff --git a/src/components/CodeEditor/CodeEditor.js b/src/components/CodeEditor/CodeEditor.js new file mode 100644 index 0000000..08e40f5 --- /dev/null +++ b/src/components/CodeEditor/CodeEditor.js @@ -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 //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 ( +
+ + +

+ {Blockly.Msg.drawer_ideerror_head} +

+

+ {Blockly.Msg.drawer_ideerror_text} +

+ +

+ {" "} + {`${error}`}{" "} +

+
+ +
+

Code Editor

+ +
+ + { + editValue(value); + }} + defaultLanguage="cpp" + defaultValue={ + localStorage.getItem("ArduinoCode") + ? localStorage.getItem("ArduinoCode") + : ` +#include //needs to be always included + +void setup () { + +} + +void loop() { + +}` + } + value={fileContent} + onMount={(editor, monaco) => { + editorRef.current = editor; + }} + /> +
+ + + + + + + + +
{Blockly.Msg.codeeditor_compile_progress}
+
{" "} + { + setResetDialog(false); + }} + onClick={() => { + setResetDialog(false); + }} + button={Blockly.Msg.button_cancel} + > + {" "} +
+ +
+
+
+
+
+ ); +}; + +export default withRouter(CodeEditor); diff --git a/src/components/CodeEditor/Compile.js b/src/components/CodeEditor/Compile.js new file mode 100644 index 0000000..26a9d4b --- /dev/null +++ b/src/components/CodeEditor/Compile.js @@ -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 ( +
+ {this.props.iconButton ? ( + + this.compile()} + > + + + + ) : ( + + )} + + {this.props.platform === false ? ( + +
+ copyimage +

{Blockly.Msg.compile_overlay_head}

+

{Blockly.Msg.compile_overlay_text}

+

+ {Blockly.Msg.compile_overlay_help} + + FAQ + +

+ +
+
+ ) : ( + +
+ {/* copyimage */} +

Dein Code wird kompiliert!

+

übertrage ihn anschließend mithlfe der senseBoxConnect-App

+

+ {Blockly.Msg.compile_overlay_help} + + FAQ + +

+ +
+
+ )} + +

+ {Blockly.Msg.drawer_ideerror_head} +

+

+ {Blockly.Msg.drawer_ideerror_text} +

+ +

+ {" "} + {`${this.state.error}`}{" "} +

+
+ +
+

Dein Code wurde erfolgreich kompiliert

+ + + +
+
+
+ ); + } +} + +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) +); diff --git a/src/components/CodeEditor/SaveIcon.js b/src/components/CodeEditor/SaveIcon.js new file mode 100644 index 0000000..c776485 --- /dev/null +++ b/src/components/CodeEditor/SaveIcon.js @@ -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 }) => ( + +
+ {loading && ( + + )} + +
+
+); + +export default SaveIcon; diff --git a/src/components/CodeEditor/SerialMonitor.js b/src/components/CodeEditor/SerialMonitor.js new file mode 100644 index 0000000..0636301 --- /dev/null +++ b/src/components/CodeEditor/SerialMonitor.js @@ -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 ( + <> +
+ + + +
+
+ {serialPortContent.map((log) => { + return ( +

+ {checked && ( + {log[0].toISOString()} + )} + + {log[1]} +

+ ); + })} +
+ + ); +}; + +export default SerialMonitor; diff --git a/src/components/CodeEditor/Sidebar.js b/src/components/CodeEditor/Sidebar.js new file mode 100644 index 0000000..5e2b976 --- /dev/null +++ b/src/components/CodeEditor/Sidebar.js @@ -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 ( +
+ {"serial" in navigator ? ( + + + Serial Monitor + + + + + + + + ) : null} + {/* + + Beispiele + + + + {examples.map((object, i) => { + return ( + + ); + })} + + + */} + {user ? ( + + + Deine openSenseMap Codes + + + + {user.boxes.map((box, i) => { + return ( + + ); + })} + + + + ) : null} + + + {Blockly.Msg.codeeditor_libraries_head} + + + +

{Blockly.Msg.codeeditor_libraries_text}

+ {LibraryVersions().map((object, i) => { + return ( +

+ + {object.library} {object.version} + +

+ ); + })} +
+
+
+
+ ); +}; + +export default Sidebar; diff --git a/src/components/CodeViewer.js b/src/components/CodeViewer.js index 856272c..e7506ac 100644 --- a/src/components/CodeViewer.js +++ b/src/components/CodeViewer.js @@ -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 ( - + - {curlyBrackets} -
{Blockly.Msg.codeviewer_arduino}
+ + {curlyBrackets} + +
+ {Blockly.Msg.codeviewer_arduino} +
- -
-              
-                {this.props.arduino}
-              
-            
+ +
- {unequal} -
{Blockly.Msg.codeviewer_xml}
+ + {unequal} + +
+ {Blockly.Msg.codeviewer_xml} +
- -
-              
-                {`${this.props.xml}`}
-              
-            
+ +
); - }; + } } 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)); diff --git a/src/components/Content.js b/src/components/Content.js index f4b7bef..a0a8d7a 100644 --- a/src/components/Content.js +++ b/src/components/Content.js @@ -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); diff --git a/src/components/Cookies.js b/src/components/Cookies.js index b943f5a..87a25c8 100644 --- a/src/components/Cookies.js +++ b/src/components/Cookies.js @@ -9,7 +9,7 @@ class Cookies extends Component { return ( ({ + 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 ( + +
+ + + this.onclick(e, "mcu")}> + + +

senseBox MCU

+
+ + {/* + this.onclick(e, "esp")}> + + +

Sensebox ESP

+
*/} + + this.onclick(e, "mini")}> + + +

senseBox MCU:mini

+
+
+
+ this.onChange(e)} + name="dialog" + color="primary" + /> + } + label={Blockly.Msg.deviceselection_keep_selection} + /> + + {Blockly.Msg.deviceselection_footnote} Arduino UNO {Blockly.Msg.deviceselection_footnote_02} senseBox MCU + +
+ ); + } +} + +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)); diff --git a/src/components/Dialog.js b/src/components/Dialog.js index 0dc7180..41d8aae 100644 --- a/src/components/Dialog.js +++ b/src/components/Dialog.js @@ -24,7 +24,7 @@ class Dialog extends Component { {this.props.actions ? this.props.actions : - } diff --git a/src/components/Faq.js b/src/components/Faq.js index 4269c1e..2909fc7 100644 --- a/src/components/Faq.js +++ b/src/components/Faq.js @@ -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 ( -
- - -
-

FAQ

- {FaqQuestions().map((object, i) => { - return ( - this.handleChange(`panel${i}`)}> - - } - > - {object.question} - - - - - - - - - ) - })} - { - this.props.button ? - - : - - } -
-
-
- ); - }; + render() { + const { panel } = this.state; + return ( +
+ + +
+

FAQ

+ {FaqQuestions().map((object, i) => { + return ( + this.handleChange(`panel${i}`)} + > + } + > + {object.question} + + + + + + + + ); + })} + {this.props.button ? ( + + ) : ( + + )} +
+
+
+ ); + } } export default withRouter(Faq); - /* - this.handleChange('panel1')}> - -} -> -{Blockly.Msg.faq_q1_question} - - - - - - - - - this.handleChange('panel2')}> - -} -> -Frage 2 - - - -Donec placerat, lectus sed mattis semper, neque lectus feugiat lectus, varius pulvinar -diam eros in elit. Pellentesque convallis laoreet laoreet. - - - - this.handleChange('panel3')}> - -} -> -Frage 3 - - - -Nunc vitae orci ultricies, auctor nunc in, volutpat nisl. Integer sit amet egestas eros, -vitae egestas augue. Duis vel est augue. - - - - this.handleChange('panel4')}> - -} -> -Frage 4 - - - -Nunc vitae orci ultricies, auctor nunc in, volutpat nisl. Integer sit amet egestas eros, -vitae egestas augue. Duis vel est augue. - - - -*/ - - // {{ - // this.props.button ? - // - // : - // - // }} - - diff --git a/src/components/Home.js b/src/components/Home.js index 171a61f..13a4661 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -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 { ) : null} +
+ {/* */} +
{this.props.project ? ( @@ -169,7 +173,10 @@ class Home extends Component { initialXml={this.props.project.xml} /> ) : ( - + )}
@@ -180,7 +187,8 @@ class Home extends Component { ) : null} - + + {/* */} {this.props.platform ? ( ({ diff --git a/src/components/Navbar.js b/src/components/Navbar.js index 8977637..228e7bb 100644 --- a/src/components/Navbar.js +++ b/src/components/Navbar.js @@ -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 {
{[ + { + 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 { - + {item.text === "Code Editor" ? ( + + + + ) : ( + + )} ); @@ -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, }; diff --git a/src/components/Route/PrivateRoute.js b/src/components/Route/PrivateRoute.js index 0d41355..92fe708 100644 --- a/src/components/Route/PrivateRoute.js +++ b/src/components/Route/PrivateRoute.js @@ -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 ? ( - this.props.isAuthenticated ? ( - this.props.children - ) : (()=>{ - return ( - - ) - })() + this.props.isAuthenticated + ? this.props.children + : (() => { + return ( + + ); + })() } - /> : 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)); diff --git a/src/components/Route/Routes.js b/src/components/Route/Routes.js index 8bd9239..cccae7c 100644 --- a/src/components/Route/Routes.js +++ b/src/components/Route/Routes.js @@ -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 { + + + {/* Sharing */} @@ -100,7 +104,7 @@ class Routes extends Component { } Home.propTypes = { - visitPage: PropTypes.func.isRequired, + visitPage: PropTypes.func, }; export default connect(null, { visitPage })(withRouter(Routes)); diff --git a/src/components/Settings/DeviceSelector.js b/src/components/Settings/DeviceSelector.js new file mode 100644 index 0000000..b506527 --- /dev/null +++ b/src/components/Settings/DeviceSelector.js @@ -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( +
+ {Blockly.Msg.settings_board} + {Blockly.Msg.settings_board_text} + + {Blockly.Msg.settings_board} + + +
+ ); + } +} + +DeviceSelector.propTypes = { + setBoard: PropTypes.func.isRequired, + selectedBoard: PropTypes.string.isRequired +}; + +const mapStateToProps = state => ({ + selectedBoard: state.board.board +}); + +export default connect(mapStateToProps, { setBoard })(DeviceSelector); diff --git a/src/components/Settings/Settings.js b/src/components/Settings/Settings.js index ad1dde6..a0f9361 100644 --- a/src/components/Settings/Settings.js +++ b/src/components/Settings/Settings.js @@ -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 { + + + - - ) - })() + {this.state.checked && this.state.xml + ? (() => { + return ( +
+ + + + + + +
+ ); + })() : null} ); - }; + } } 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)); diff --git a/src/components/Tutorial/Builder/Builder.js b/src/components/Tutorial/Builder/Builder.js index 12803c2..d603e00 100644 --- a/src/components/Tutorial/Builder/Builder.js +++ b/src/components/Tutorial/Builder/Builder.js @@ -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 (
{filteredTutorials.map((tutorial) => ( - {tutorial.title} + + {tutorial.title}{" "} + {tutorial.review && tutorial.public === false ? ( +
+ + +
+ ) : tutorial.public === false ? ( + + ) : null} +
+ /* ) : tutorial.public === false ? ( + + {tutorial.title} + + ) : ( + {tutorial.title} + )} */ ))} @@ -487,6 +519,45 @@ class Builder extends Component { label={"Titel"} error={this.props.error.title} /> +
+ + + + + + + {this.props.user.blocklyRole === "admin" ? ( + + ) : null} +
{this.props.steps.map((step, 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, diff --git a/src/components/Tutorial/Builder/Difficulty.js b/src/components/Tutorial/Builder/Difficulty.js new file mode 100644 index 0000000..8dc21df --- /dev/null +++ b/src/components/Tutorial/Builder/Difficulty.js @@ -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 ( + + + {Blockly.Msg.builder_difficulty} + + + } + halfIcon={} + fullIcon={} + activeColor="#ffd700" + /> + } + label="Schwierigkeitsgrad" + labelPlacement="start" + /> + + + ); + } +} + +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)); diff --git a/src/components/Tutorial/Builder/Hardware.js b/src/components/Tutorial/Builder/Hardware.js index e4b63c6..b9e2a7b 100644 --- a/src/components/Tutorial/Builder/Hardware.js +++ b/src/components/Tutorial/Builder/Hardware.js @@ -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 ( -
- Hardware - {Blockly.Msg.builder_hardware_order} - {this.props.error ? {Blockly.Msg.builder_hardware_helper} : null} - +
+ Hardware + + {Blockly.Msg.builder_hardware_order} + + {this.props.error ? ( + + {Blockly.Msg.builder_hardware_helper} + + ) : null} + {hardware.map((picture, i) => ( - this.onChange(picture.id)} classes={{ tile: this.props.value.filter(value => value === picture.id).length > 0 ? this.props.classes.active : this.props.classes.border }}> -
- {picture.name} + this.onChange(picture.id)} + classes={{ + item: + this.props.value.filter((value) => value === picture.id) + .length > 0 + ? this.props.classes.active + : this.props.classes.border, + }} + > +
+ {picture.name}
- +
{picture.name}
} /> - +
))} - +
); - }; + } } 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))); diff --git a/src/components/Tutorial/Builder/MarkdownEditor.js b/src/components/Tutorial/Builder/MarkdownEditor.js new file mode 100644 index 0000000..12ed3c3 --- /dev/null +++ b/src/components/Tutorial/Builder/MarkdownEditor.js @@ -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 ( + + mdParser.render(text)} + onChange={handleChange} + value={value} + id={props.property} + label={props.label} + property={props.property} + onImageUpload={uploadImage} + /> + + ); +}; + +export default connect(null, { + tutorialTitle, + jsonString, + changeContent, + setError, + deleteError, +})(MarkdownEditor); diff --git a/src/components/Tutorial/Builder/Public.js b/src/components/Tutorial/Builder/Public.js new file mode 100644 index 0000000..34204b4 --- /dev/null +++ b/src/components/Tutorial/Builder/Public.js @@ -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 ( + + + {Blockly.Msg.builder_public_head} + + + + } + label={Blockly.Msg.builder_public_label} + labelPlacement="start" + /> + + + ); + } +} + +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)); diff --git a/src/components/Tutorial/Builder/Review.js b/src/components/Tutorial/Builder/Review.js new file mode 100644 index 0000000..add30e0 --- /dev/null +++ b/src/components/Tutorial/Builder/Review.js @@ -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 ( + + + {Blockly.Msg.builder_review_head} + + {Blockly.Msg.builder_review_text} + + + } + label="Ich möchte mein Tutorial öffentlich machen" + labelPlacement="start" + /> + + + ); + } +} + +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)); diff --git a/src/components/Tutorial/Builder/Step.js b/src/components/Tutorial/Builder/Step.js index ba0e18c..35ed9f7 100644 --- a/src/components/Tutorial/Builder/Step.js +++ b/src/components/Tutorial/Builder/Step.js @@ -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 ( -
- Schritt {index+1} -
-
- +
+ + Schritt {index + 1} + +
+
+ this.props.addStep(index+1)} + style={index === 0 ? {} : { marginBottom: "5px" }} + onClick={() => this.props.addStep(index + 1)} > - + - {index !== 0 ? + {index !== 0 ? (
- + this.props.changeStepIndex(index, index-1)} + style={{ marginBottom: "5px" }} + onClick={() => this.props.changeStepIndex(index, index - 1)} > - + - + this.props.changeStepIndex(index, index+1)} + style={{ marginBottom: "5px" }} + onClick={() => this.props.changeStepIndex(index, index + 1)} > - + - + this.props.removeStep(index)} > - +
- : null} + ) : null}
-
+
- - - {index === 0 ? + + + {index === 0 ? (
- - + +
- : null} - {this.props.step.type === 'instruction' ? - - : null} - + ) : null} +
); - }; + } } 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)); diff --git a/src/components/Tutorial/Builder/Textfield.js b/src/components/Tutorial/Builder/Textfield.js index 2cd3bd3..80d69ee 100644 --- a/src/components/Tutorial/Builder/Textfield.js +++ b/src/components/Tutorial/Builder/Textfield.js @@ -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") { diff --git a/src/components/Tutorial/Hardware.js b/src/components/Tutorial/Hardware.js index a8ce1f1..4eb2416 100644 --- a/src/components/Tutorial/Hardware.js +++ b/src/components/Tutorial/Hardware.js @@ -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 ( -
+
{Blockly.Msg.tutorials_hardware_head} - + {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 ( - -
- {hardwareInfo.name} this.handleClickOpen(hardwareInfo)} /> + +
+ {hardwareInfo.name} this.handleClickOpen(hardwareInfo)} + />
- +
{hardwareInfo.name}
} actionIcon={ - this.handleClickOpen(hardwareInfo)}> + this.handleClickOpen(hardwareInfo)} + > } /> - - ) +
+ ); })} - +
- {this.state.hardwareInfo.name} - {Blockly.Msg.tutorials_hardware_moreInformation} {Blockly.Msg.tutorials_hardware_here}. + {this.state.hardwareInfo.name} + {Blockly.Msg.tutorials_hardware_moreInformation}{" "} + + {Blockly.Msg.tutorials_hardware_here} + + .
-
); - }; + } } export default withWidth()(withStyles(styles, { withTheme: true })(Hardware)); diff --git a/src/components/Tutorial/HintTutorialExists.js b/src/components/Tutorial/HintTutorialExists.js index 082926b..ae423b3 100644 --- a/src/components/Tutorial/HintTutorialExists.js +++ b/src/components/Tutorial/HintTutorialExists.js @@ -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 (
- {Blockly.Msg.messages_newblockly_text} + + {Blockly.Msg.messages_newblockly_text} +
this.onChange(e)} @@ -81,15 +85,18 @@ class HintTutorialExists extends Component { />
); - }; + } } 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)); diff --git a/src/components/Tutorial/Instruction.js b/src/components/Tutorial/Instruction.js index fc72447..869e7e1 100644 --- a/src/components/Tutorial/Instruction.js +++ b/src/components/Tutorial/Instruction.js @@ -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 (
- - {step.headline} - {step.text} diff --git a/src/components/Tutorial/Requirement.js b/src/components/Tutorial/Requirement.js index 87784cc..42e3165 100644 --- a/src/components/Tutorial/Requirement.js +++ b/src/components/Tutorial/Requirement.js @@ -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 ( -
+
{Blockly.Msg.tutorials_requirements} {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 ( - - + +
-
- - {error || success === 1 ? - - : } - {success < 1 && !error ? - - - : null} +
+ + {error || success === 1 ? ( + + ) : ( + + )} + {success < 1 && !error ? ( + + ) : null}
-
+
- {error || success === 1 ? - - : 0 ? this.props.classes.outerDivSuccess : {}}>{Math.round(success * 100)}% - } + {error || success === 1 ? ( + + ) : ( + 0 + ? this.props.classes.outerDivSuccess + : {} + } + > + {Math.round(success * 100)}% + + )}
-
- {title} +
+ + {title} +
- ) - } - )} + ); + })}
); - }; + } } 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))); diff --git a/src/components/Tutorial/SolutionCheck.js b/src/components/Tutorial/SolutionCheck.js index 5bffaab..0f1ddeb 100644 --- a/src/components/Tutorial/SolutionCheck.js +++ b/src/components/Tutorial/SolutionCheck.js @@ -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 { this.check()} > - + - {this.state.msg.type === 'success' ? -
+ {this.state.msg.type === "success" ? ( +
- {this.props.activeStep === steps.length - 1 ? + {this.props.activeStep === steps.length - 1 ? ( - : + ) : ( - } + )}
- : null} + ) : null}
-
); - }; + } } - 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)) +); diff --git a/src/components/Tutorial/StepperHorizontal.js b/src/components/Tutorial/StepperHorizontal.js index f045621..ce4083f 100644 --- a/src/components/Tutorial/StepperHorizontal.js +++ b/src/components/Tutorial/StepperHorizontal.js @@ -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 ( -
- {error || success > 0 ? -
-
- : null} - {success < 1 && !error ? -
-
- : null} +
+ {error || success > 0 ? ( +
+ ) : null} + {success < 1 && !error ? ( +
+ ) : null}
- +
- {tutorialStatus !== 'Other' ?
: null} - {title} + {tutorialStatus !== "Other" ? ( +
+ +
+ ) : null} + + {title} + {title !== this.props.tutorial.steps[activeStep].headline + ? ` - ${this.props.tutorial.steps[activeStep].headline}` + : null} +
); - }; + } } 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))); diff --git a/src/components/Tutorial/StepperVertical.js b/src/components/Tutorial/StepperVertical.js index 5735c3f..b1fa614 100644 --- a/src/components/Tutorial/StepperVertical.js +++ b/src/components/Tutorial/StepperVertical.js @@ -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 ( -
+
} - 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 ( - -
{ this.props.tutorialStep(i)}}> + +
{ + this.props.tutorialStep(i); + } + } + > - + >
- )})} + ); + })}
); - }; + } } - 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)) +); diff --git a/src/components/Tutorial/TutorialHome.js b/src/components/Tutorial/TutorialHome.js index ee91c30..ab94f51 100644 --- a/src/components/Tutorial/TutorialHome.js +++ b/src/components/Tutorial/TutorialHome.js @@ -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 : (
-

{Blockly.Msg.tutorials_home_head}

+

Alle Tutorials

- {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 ( {tutorial.title} +
+ {this.props.user ? ( +
+

User Tutorials

+ + {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 ( + + + + {tutorial.title} + + + +

+ Creator:{tutorial.creator}
+

+ + { + navigator.clipboard.writeText( + `${window.location.origin}/tutorial/${tutorial._id}` + ); + this.setState({ + snackbar: true, + key: Date.now(), + message: + Blockly.Msg.messages_copylink_success, + type: "success", + }); + }} + > + + + + + + + + + {tutorial.review ? ( + + + + + + ) : null} + +
+

+
+
+ ); + })} +
+
+ ) : null}
); } @@ -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, diff --git a/src/components/User/Login.js b/src/components/User/Login.js index bbfac8b..4848dce 100644 --- a/src/components/User/Login.js +++ b/src/components/User/Login.js @@ -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: "", diff --git a/src/components/Workspace/AutoSave.js b/src/components/Workspace/AutoSave.js new file mode 100644 index 0000000..950cc8e --- /dev/null +++ b/src/components/Workspace/AutoSave.js @@ -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 ( +
+ +
+ ); + } +} + +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); diff --git a/src/components/Workspace/Compile.js b/src/components/Workspace/Compile.js index 1bea26c..26a9d4b 100644 --- a/src/components/Workspace/Compile.js +++ b/src/components/Workspace/Compile.js @@ -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()} > - + ) : ( diff --git a/src/components/Workspace/CopyCode.js b/src/components/Workspace/CopyCode.js index 99b148b..02d255d 100644 --- a/src/components/Workspace/CopyCode.js +++ b/src/components/Workspace/CopyCode.js @@ -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 ( -
- {this.props.iconButton ? - - this.copyCode()} - > - - - - : - - } - - -
- ); + 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 ( +
+ {this.props.iconButton ? ( + + this.copyCode()} + > + + + + ) : ( + + )} + +
+ ); + } } 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) +); diff --git a/src/components/Workspace/Screenshot.js b/src/components/Workspace/Screenshot.js index 3dfc29b..c549a17 100644 --- a/src/components/Workspace/Screenshot.js +++ b/src/components/Workspace/Screenshot.js @@ -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 = ''; - var bbox = document.getElementsByClassName("blocklyBlockCanvas")[0].getBBox(); + var css = + '"; + var bbox = document + .getElementsByClassName("blocklyBlockCanvas")[0] + .getBBox(); var content = new XMLSerializer().serializeToString(canvas); var xml = ` ${css}">${content}`; 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 {
); - }; + } } 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)); diff --git a/src/components/Workspace/ShareProject.js b/src/components/Workspace/ShareProject.js index 8b962cc..d13c194 100644 --- a/src/components/Workspace/ShareProject.js +++ b/src/components/Workspace/ShareProject.js @@ -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 (
this.shareBlocks()} > @@ -115,44 +182,134 @@ class WorkspaceFunc extends Component { onClose={this.toggleDialog} onClick={this.toggleDialog} button={Blockly.Msg.button_close} - > -
- Über den folgenden Link kannst du dein Programm teilen: - this.toggleDialog()} className={this.props.classes.link}>{`${window.location.origin}/share/${this.state.id}`} - - { - 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' }); - }} - > - - - - {this.props.project && this.props.project.shared && this.props.message.id !== 'SHARE_SUCCESS' ? - {`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.`} - : {`Der Link ist nun ${process.env.REACT_APP_SHARE_LINK_EXPIRES} Tage gültig.`}} -
+ > + { this.state.isFetching ? ( +
+ +
+ ) : ( +
+ + Über den folgenden Link kannst du dein Programm teilen: + +
+ this.toggleDialog()} + className={this.props.classes.link} + target="_blank" + rel="noreferrer" + >{this.state.shortLink} + + { + navigator.clipboard.writeText( + this.state.shortLink + ); + this.setState({ + snackbar: true, + key: Date.now(), + message: Blockly.Msg.messages_copylink_success, + type: "success", + }); + }} + > + + + +
+
+ +
+
+ +
+
+ + + + + + + + + + + + +
+ { this.props.project && + this.props.project.shared && + this.props.message.id !== "SHARE_SUCCESS" ? ( + + {`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.`} + + ) : ( + + {`Der Link ist nun ${process.env.REACT_APP_SHARE_LINK_EXPIRES} Tage gültig.`} + + ) + } +
+ ) + }
); - }; + } } 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) +); diff --git a/src/components/Workspace/WorkspaceFunc.js b/src/components/Workspace/WorkspaceFunc.js index 68e3741..bec0a70 100644 --- a/src/components/Workspace/WorkspaceFunc.js +++ b/src/components/Workspace/WorkspaceFunc.js @@ -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 ( -
- - {!this.props.assessment ? +
+ {!this.props.assessment & !this.props.multiple ? : null} + {!this.props.assessment ? ( - : null} + ) : null} - {this.props.assessment ? + {this.props.assessment ? ( - : !this.props.multiple ? - - : null} + ) : !this.props.multiple ? ( + + ) : null} - {!this.props.multiple ? - - : null} + {!this.props.multiple ? : null} - - {this.props.user && !this.props.multiple ? + {this.props.user && !this.props.multiple ? ( - : null} + ) : null} - {!this.props.multiple ? - - : null} + {!this.props.multiple ? ( + + ) : null} - - {!this.props.assessment && !this.props.multiple ? + {!this.props.assessment && !this.props.multiple ? ( - : null} + ) : null} - {!this.props.assessment && !this.props.multiple ? - - : null} + {!this.props.assessment && !this.props.multiple ? ( + + ) : null} - {this.props.projectType !== 'gallery' && !this.props.assessment ? + {this.props.projectType !== "gallery" && !this.props.assessment ? ( - : null} + ) : null} - {!this.props.multiple ? - - : 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 ? ( - : null} - + ) : null}
); - }; + } } 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); diff --git a/src/components/Workspace/WorkspaceName.js b/src/components/Workspace/WorkspaceName.js index 0437f9a..d53c027 100644 --- a/src/components/Workspace/WorkspaceName.js +++ b/src/components/Workspace/WorkspaceName.js @@ -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 (
- +
{ 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) ? - {this.props.name} - : null} -
- + {this.props.name && + !isWidthDown( + this.props.projectType === "project" || + this.props.projectType === "gallery" + ? "xl" + : "xs", + this.props.width + ) ? ( + + {this.props.name} + + ) : null} +
+
@@ -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"} > -
- {this.props.projectType === 'gallery' || this.state.projectType === 'gallery' ? +
+ {this.props.projectType === "gallery" || + this.state.projectType === "gallery" ? (
- - + +
- : } - + ) : ( + + )} +
); - }; + } } 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))); diff --git a/src/components/copy.svg b/src/components/copy.svg index f09e27b..ca6a708 100644 --- a/src/components/copy.svg +++ b/src/components/copy.svg @@ -1,53 +1,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/sensebox_logo.svg b/src/components/sensebox_logo.svg index 82eb0e1..a97b681 100644 --- a/src/components/sensebox_logo.svg +++ b/src/components/sensebox_logo.svg @@ -1,3 +1,24 @@ - - -image/svg+xml \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/data/arduinoExamples.js b/src/data/arduinoExamples.js new file mode 100644 index 0000000..7d2dbe4 --- /dev/null +++ b/src/data/arduinoExamples.js @@ -0,0 +1,14 @@ +export const ArduinoExamples = () => { + return [ + { + name: "MCU Component Test", + description: "Test the different compoenents of the MCU", + code: '// senseBox:home WiFi is enabled by default!\n// If you have a senseBox:home Ethernet comment out line 5\n// and comment in line 4\n// Do not comment in both at the same time!\n//#define ENABLE_ETHERNET\n#define ENABLE_WIFI\n\n#include \n#include \n#ifdef ENABLE_WIFI\n#include \n#include \n#endif\n#ifdef ENABLE_ETHERNET\n#include \n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n/********WiFi User Settings********/\nconst char *ssid = ""; // your network SSID (name)\nconst char *pass = ""; // your network password\n/**********************************/\nchar server[] = "internet-test.testing.opensensemap.org";\n#ifdef ENABLE_WIFI\nint status = WL_IDLE_STATUS;\nWiFiClient client;\n#endif\n\n#ifdef ENABLE_ETHERNET\n/********Ethernet User Settings********/\n//Configure static IP setup (only needed if DHCP is disabled)\nbyte mac[] = {0xDE,0xAD,0xBE,0xEF,0xFE,0xED};\nIPAddress myIp(192, 168, 0, 42);\nIPAddress myDns(8, 8, 8, 8);\nIPAddress myGateway(192, 168, 0, 177);\nIPAddress mySubnet(255, 255, 255, 0);\n/**********************************/\nEthernetClient ethernetClient;\n#endif\n\n#define CMD_RESET (0x00)\n#define CMD_SEND (0x03)\n#define ECC_READ (0x02) // read command\n#define ECC_WRITE (0x12) // write command\n#define ECC_ZONE_CFG (0x00) // configuration zone\n#define ECC_ZONE_CNT_FLAG (0x80) // 1=32 bytes, 0=4 bytes\nvoid setup() {\n Serial.begin(9600);\n while(!Serial); // wait until serial is ready\n printMenu();\n // power on required ports\n senseBoxIO.powerI2C(false);\n senseBoxIO.powerXB1(false);\n senseBoxIO.powerUART(false);\n delay(250);\n senseBoxIO.powerI2C(true);\n senseBoxIO.powerXB1(true);\n senseBoxIO.powerUART(true);\n // init UART and I2C\n Serial1.begin(9600);\n Serial2.begin(9600);\n Wire.begin();\n}\nvoid loop() {\n char rx;\n if (Serial.available() > 0)\n {\n\n rx = Serial.read(); // get the character\n Serial.println("\\n\\n");\n // check if a number was received\n switch(rx)\n {\n case \'1\':\n check_uart_sensor();\n Serial.println("\\nI2C/Wire:");\n byte devices, address;\n devices = 0;\n for(address = 1; address < 127; address++ )\n {\n Wire.beginTransmission(address);\n byte error = Wire.endTransmission();\n\n if(error == 0)\n {\n devices++;\n Serial.print("Device found at 0x");\n delay(100);\n Serial.print(address, HEX);\n Serial.println();\n check_i2c_sensor(address);\n }\n else if(error==4)\n {\n Serial.print("Unknow error at 0x");\n delay(100);\n Serial.println(address, HEX);\n }\n }\n\n if(devices == 0) Serial.println("No devices found\\n");\n senseBoxIO.statusNone();\n delay(250);\n break;\n #ifdef ENABLE_WIFI\n case \'2\':\n connectionWiFiTest();\n Serial.println("");\n delay(250);\n break;\n #endif\n #ifdef ENABLE_ETHERNET\n case \'3\':\n connectionEthernetTest();\n Serial.println("");\n delay(250);\n break;\n #endif\n case \'4\':\n Serial.println("Security key:");\n getSecKey();\n Serial.println();\n delay(250);\n break;\n case \'5\':\n Serial.flush();\n NVIC_SystemReset();\n break;\n }\n Serial.flush();\n }\n}\nvoid printMenu()\n{\n Serial.println("senseBox MCU option menu\\nType one of the numbers in the input field above and hit \'Enter\'.");\n delay(100);\n Serial.println(" 1 - Find connected devices");\n #ifdef ENABLE_WIFI\n delay(100);\n Serial.println(" 2 - Test connection to openSenseMap (WiFi on XBee1)");\n #endif\n #ifdef ENABLE_ETHERNET\n delay(100);\n Serial.println(" 3 - Test connection to openSenseMap (Ethernet on XBee1)");\n #endif\n delay(100);\n Serial.println(" 4 - Get security key\\n");\n return;\n}\nvoid check_uart_sensor(){\n Serial.println("UART/Serial Port:");\n SDS011 sds1(Serial1);\n SDS011 sds2(Serial2);\n float pm10,pm25;\n int sds_error;\n sds_error = sds1.read(&pm25,&pm10);\n if (!sds_error)\n {\n Serial.println("SDS011 dust particle sensor found at serial port #1.");\n }\n else\n {\n sds_error = sds2.read(&pm25,&pm10);\n if (!sds_error)\n {\n Serial.println("SDS011 dust particle sensor found at serial port #2.");\n return;\n }\n }\n Serial.println("No device found.");\n}\nvoid check_i2c_sensor(byte address)\n{\n float t=0, h=0, p=0, a=0;\n unsigned int u=0;\n unsigned long l=0;\n Adafruit_BMP280 bmp280;\n Adafruit_BME280 bme280;\n Adafruit_BME680 bme680;\n Adafruit_HDC1000 hdc;\n if((address == 0) || (address > 127))\n {\n return;\n }\n switch(address)\n {\n case 0x29: //TSL45315\n Serial.println("--- TSL45315");\n Wire.beginTransmission(address);\n Wire.write(0x80|0x00); //control\n Wire.write(0x03);\n Wire.endTransmission();\n Wire.beginTransmission(address);\n Wire.write(0x80|0x01); //config\n Wire.write(0x02); //M=4 T=100ms\n Wire.endTransmission();\n delay(120);\n Wire.beginTransmission(address);\n Wire.write(0x80|0x04); //data low\n Wire.endTransmission();\n Wire.requestFrom((uint8_t)address, (uint8_t)2);\n delay(1);\n u |= (Wire.read()<<8);\n u |= (Wire.read()<<8);\n l = u * 4;\n Serial.print("Lux ");\n Serial.println(l, DEC);\n break;\n case 0x38: //VEML6070\n //case 0x39:\n Serial.println("--- VEML6070 (0x38+0x39)");\n Wire.beginTransmission(address);\n Wire.write((0x1<<2) | 0x02); //Integration Time 1\n Wire.endTransmission();\n delay(120);\n Wire.requestFrom((uint8_t)(address+1), (uint8_t)1); //MSB\n delay(1);\n u |= (Wire.read()<<8);\n Wire.requestFrom((uint8_t)(address+0), (uint8_t)1); //LSB\n delay(1);\n u |= (Wire.read()<<0);\n Serial.print("UV ");\n Serial.println(u, DEC);\n break;\n case 0x40: //HDC100X\n case 0x41:\n //case 0x42:\n case 0x43:\n Serial.println("--- HDC100X");\n hdc.begin(address);\n t = hdc.readTemperature();\n h = hdc.readHumidity();\n Serial.print("Temp ");\n Serial.print(t, DEC);\n Serial.println(" *C");\n Serial.print("Humi ");\n Serial.print(h, DEC);\n Serial.println(" %");\n break;\n case 0x76: //BMP280 or BME280 or BME680\n case 0x77:\n if(bmp280.begin(address) != 0)\n {\n Serial.println("--- BMP280");\n delay(100);\n t = bmp280.readTemperature();\n p = bmp280.readPressure();\n a = bmp280.readAltitude(1013.25); //1013.25 = sea level pressure\n }\n else if(bme280.begin(address) != 0)\n {\n Serial.println("--- BME280");\n delay(100);\n t = bme280.readTemperature();\n p = bme280.readPressure();\n a = bme280.readAltitude(1013.25); //1013.25 = sea level pressure\n h = bme280.readHumidity();\n }\n else if(bme680.begin(address) != 0)\n {\n Serial.println("--- BME680");\n delay(100);\n bme680.performReading();\n t = bme680.temperature;\n p = bme680.pressure;\n a = bme680.readAltitude(1013.25); //1013.25 = sea level pressure\n h = bme680.humidity;\n u = bme680.gas_resistance / 1000.0;\n }\n else\n {\n Wire.beginTransmission(address);\n Wire.write(0xD0); //chip id\n Wire.endTransmission();\n Wire.requestFrom(address, (byte)1);\n delay(1);\n u = Wire.read();\n if(u == 0x58) //BMP280\n {\n Serial.println("--- BMP280");\n }\n else if(u == 0x60) //BME280\n {\n Serial.println("--- BME280");\n }\n else if(u == 0x61) //BME680\n {\n Serial.println("--- BME680");\n }\n }\n Serial.print("Temp ");\n Serial.print(t, DEC);\n Serial.println(" *C");\n Serial.print("Pres ");\n Serial.print(p/100.0, DEC);\n Serial.println(" hPa");\n Serial.print("Alti ");\n Serial.print(a, DEC);\n Serial.println(" m");\n if(h != 0)\n {\n Serial.print("Humi ");\n Serial.print(h, DEC);\n Serial.println(" %");\n }\n if(u != 0)\n {\n Serial.print("Gas ");\n Serial.print(u, DEC);\n Serial.println(" kOhm");\n }\n break;\n case 0x42: //CAM-M8Q\n Serial.println("--- CAM-M8Q");\n break;\n case 0x50: //24LCxxx EEPROM\n Serial.println("--- 24LCxxx");\n break;\n case 0x60: //ATECCx08\n Serial.println("--- ATECCx08");\n break;\n case 0x68: //RV8523\n Serial.println("--- RV8523");\n break;\n }\n delay(250); //wait 250ms\n}\nvoid getSecKey()\n{\n Wire1.begin();\n // init ATECC\n write(CMD_RESET, 0x00); // reset\n delay(100); // wait 100ms\n // read config zone\n byte buf[64]; // buffer\n buf[0] = 5+2; // length: data + 2 crc bytes\n buf[1] = ECC_READ; // cmd\n buf[2] = ECC_ZONE_CFG|ECC_ZONE_CNT_FLAG; // param 1\n buf[3] = 0x00; // addr lsb\n buf[4] = 0x00; // addr msb\n //buf[5] = 0x00; // crc\n //buf[6] = 0x00; // crc\n calc_crc(buf, buf[0]-2, &buf[5]); // calc crc\n write(CMD_SEND, buf, buf[0]); // send cmd\n delay(10); // wait 10ms\n read(buf, sizeof(buf)); // read response\n Serial.print("0");\n Serial.print(buf[1], HEX); Serial.print(" ");\n Serial.print(buf[2], HEX); Serial.print(" ");\n Serial.print(buf[3], HEX); Serial.print(" ");\n Serial.print(buf[4], HEX); Serial.print(" ");\n Serial.print(buf[ 9], HEX); Serial.print(" ");\n Serial.print(buf[10], HEX); Serial.print(" ");\n Serial.print(buf[11], HEX); Serial.print(" ");\n Serial.print(buf[12], HEX); Serial.print(" ");\n Serial.print(buf[13], HEX); Serial.print(" ");\n Serial.println("");\n}\nvoid read(byte *data, byte max_len)\n{\n byte len;\n Wire1.requestFrom(I2C_ATECC, 1); // request length\n while(Wire1.available() == 0); // wait for data bytes\n len = Wire1.read();\n *data++ = len;\n if(len)\n {\n Wire1.requestFrom(I2C_ATECC, len); // request x bytes\n while(Wire1.available() == 0); // wait for data bytes\n delay(10); // wait 10ms\n for(byte i = 0; (i < len) && (i < max_len); i++)\n {\n *data++ = Wire1.read(); // read data byte\n }\n }\n}\nvoid write(byte reg, byte *data, byte len)\n{\n Wire1.beginTransmission(I2C_ATECC); // start transmission\n Wire1.write(reg); // write register byte\n for(; len != 0; len--)\n {\n Wire1.write(*data++); // write data byte\n }\n Wire1.endTransmission(); // stop transmission\n}\nvoid write(byte reg, byte data)\n{\n Wire1.beginTransmission(I2C_ATECC); // start transmission\n Wire1.write(reg); // write register byte\n Wire1.write(data); // write data byte\n Wire1.endTransmission(); // stop transmission\n}\nvoid calc_crc(byte *data, byte len, byte *crc)\n{\n uint8_t i, shift_reg, data_bit, crc_bit;\n uint16_t crc_reg = 0;\n uint16_t polynom = 0x8005;\n for(i = 0; i < len; i++)\n {\n for(shift_reg = 0x01; shift_reg > 0x00; shift_reg <<= 1)\n {\n data_bit = (data[i] & shift_reg) ? 1 : 0;\n crc_bit = crc_reg >> 15;\n crc_reg <<= 1;\n if(data_bit != crc_bit)\n {\n crc_reg ^= polynom;\n }\n }\n }\n crc[0] = (byte)(crc_reg & 0x00FF);\n crc[1] = (byte)(crc_reg >> 8);\n}\nvoid connectionWiFiTest(){\n #ifdef ENABLE_WIFI\n if (WiFi.status() == WL_NO_SHIELD)\n {\n Serial.println("WiFi bee not present");\n return;\n }\n Serial.println("Check WiFi firmware:");\n Serial.println("====================");\n // Print firmware version on the shield\n String fv = WiFi.firmwareVersion();\n String latestFv;\n Serial.print("Firmware version installed: ");\n Serial.println(fv);\n\n if (REV(GET_CHIPID()) >= REV_3A0) {\n // model B\n latestFv = WIFI_FIRMWARE_LATEST_MODEL_B;\n } else {\n // model A\n latestFv = WIFI_FIRMWARE_LATEST_MODEL_A;\n }\n\n // Print required firmware version\n Serial.print("Latest firmware version available : ");\n Serial.println(latestFv);\n\n // Check if the latest version is installed\n Serial.println();\n if (fv == latestFv || fv == "19.5.2") {\n Serial.println("Check result: PASSED");\n } else {\n Serial.println("Check result: NOT PASSED");\n Serial.println(" - The firmware version on the shield do not match the");\n Serial.println(" version required by the library, you may experience");\n Serial.println(" issues or failures.");\n Serial.println(" - Update the firmware at least to version 19.5.2");\n }\n\n Serial.println();\n Serial.println("Check internet connectivity:");\n Serial.println("============================");\n\n if (WiFi.status() != WL_CONNECTED) {\n Serial.print("Connecting to WiFi...");\n delay(1000); // wait 1s\n WiFi.begin(ssid, pass);\n delay(5000); // wait 5s\n }\n if (WiFi.status() == WL_CONNECTED) Serial.println("connected!");\n else\n {\n Serial.println("failed! Please check SSID and password.");\n return;\n }\n for (uint8_t timeout = 2; timeout != 0; timeout--)\n {\n Serial.print("Calling openSenseMap server...");\n if (client.connect(server, 80))\n {\n Serial.println("connected!");\n // Make a HTTP request:\n client.println("GET / HTTP/1.1");\n client.print("Host: ");\n client.println(server);\n client.println("Connection: close");\n client.println();\n }\n break;\n }\n if(client.connected())\n {\n // wait for server response\n Serial.println("Server response:\\n");\n while (!client.available())\n {\n delay(1);\n }\n // read server response\n while (client.available())\n {\n char c = client.read();\n Serial.write(c);\n }\n Serial.print("\\n");\n Serial.println("Disconnecting from server.");\n client.flush();\n client.stop();\n }else Serial.println("failed after 3 trys!");\n Serial.println("Disconnecting from WiFi.");\n WiFi.disconnect();\n #endif\n}\n\nvoid connectionEthernetTest() {\n #ifdef ENABLE_ETHERNET\n Ethernet.init(PIN_XB1_CS);\n Serial.println("Trying to initialize DHCP...");\n if (Ethernet.begin(mac) == 0) {\n Serial.println("Failed to configure Ethernet using DHCP");\n // start the Ethernet connection using a fixed IP address and DNS server:\n Serial.println("Trying Ethernet connection using a fixed IP address and DNS server");\n Ethernet.begin(mac, myIp, myDns, mySubnet);\n } else {\n // print your local IP address:\n Serial.println("DHCP is working.");\n Serial.print("My IP address: ");\n for (byte thisByte = 0; thisByte < 4; thisByte++) {\n // print the value of each byte of the IP address:\n Serial.print(Ethernet.localIP()[thisByte], DEC);\n Serial.print(".");\n }\n Serial.println();\n }\n for (uint8_t timeout = 2; timeout != 0; timeout--)\n {\n Serial.print("Calling openSenseMap server...");\n if (ethernetClient.connect(server, 80))\n {\n Serial.println("connected!");\n // Make a HTTP request:\n ethernetClient.println("GET / HTTP/1.1");\n ethernetClient.print("Host: ");\n ethernetClient.println(server);\n ethernetClient.println("Connection: close");\n ethernetClient.println();\n }\n break;\n }\n if(ethernetClient.connected())\n {\n // wait for server response\n Serial.println("Server response:\\n");\n while (!ethernetClient.available())\n {\n delay(1);\n }\n // read server response\n while (ethernetClient.available())\n {\n char c = ethernetClient.read();\n Serial.write(c);\n }\n Serial.print("\\n");\n Serial.println("Disconnecting from server.");\n ethernetClient.flush();\n ethernetClient.stop();\n }\n else Serial.println("failed after 3 trys!");\n #endif\n}', + }, + { + name: "ArduinoBearSSL", + description: "BearSSL is a TLS/SSL library for Arduino", + code: "", + }, + ]; +}; diff --git a/src/data/hardware.json b/src/data/hardware.json index de7aefc..6149f46 100644 --- a/src/data/hardware.json +++ b/src/data/hardware.json @@ -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", diff --git a/src/data/versions.js b/src/data/versions.js new file mode 100644 index 0000000..984ede6 --- /dev/null +++ b/src/data/versions.js @@ -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", + }, + ]; +}; diff --git a/src/index.js b/src/index.js index 34c2f28..fad5562 100644 --- a/src/index.js +++ b/src/index.js @@ -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( , - document.getElementById('root') + document.getElementById("root") ); // If you want your app to work offline and load faster, you can change diff --git a/src/reducers/boardReducer.js b/src/reducers/boardReducer.js new file mode 100644 index 0000000..b2c157f --- /dev/null +++ b/src/reducers/boardReducer.js @@ -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; + } +} diff --git a/src/reducers/generalReducer.js b/src/reducers/generalReducer.js index 1d1acfe..7cde579 100644 --- a/src/reducers/generalReducer.js +++ b/src/reducers/generalReducer.js @@ -25,7 +25,7 @@ const initialSounds = () => { if (window.localStorage.getItem("sounds")) { return window.localStorage.getItem("sounds"); } else { - return "off"; + return false; } }; diff --git a/src/reducers/index.js b/src/reducers/index.js index cdedf04..57e99e5 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -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, diff --git a/src/reducers/tutorialBuilderReducer.js b/src/reducers/tutorialBuilderReducer.js index 1eb80f4..cb3a9dc 100644 --- a/src/reducers/tutorialBuilderReducer.js +++ b/src/reducers/tutorialBuilderReducer.js @@ -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, diff --git a/src/reducers/tutorialReducer.js b/src/reducers/tutorialReducer.js index a44a31f..b60f7f7 100644 --- a/src/reducers/tutorialReducer.js +++ b/src/reducers/tutorialReducer.js @@ -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; } diff --git a/src/store.js b/src/store.js index bf6460e..4fedfbb 100644 --- a/src/store.js +++ b/src/store.js @@ -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__() ) );