Merge branch 'development' into markdown_file_upload

This commit is contained in:
Mario Pesch 2022-02-18 11:26:58 +01:00 committed by GitHub
commit b0ab03d95b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 2184 additions and 44864 deletions

2
.env
View File

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

44157
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,44 +1,51 @@
{ {
"name": "blockly-react", "name": "blockly-react",
"version": "0.1.0", "version": "1.0.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@blockly/block-plus-minus": "^2.0.10", "@blockly/block-plus-minus": "^3.0.5",
"@blockly/field-grid-dropdown": "^1.0.25", "@blockly/field-grid-dropdown": "^1.0.31",
"@blockly/field-slider": "^2.1.1", "@blockly/field-slider": "^3.0.5",
"@blockly/plugin-scroll-options": "^1.0.2", "@blockly/plugin-scroll-options": "^2.0.5",
"@blockly/plugin-typed-variable-modal": "^3.1.26", "@blockly/plugin-typed-variable-modal": "^4.0.5",
"@blockly/zoom-to-fit": "^2.0.7", "@blockly/workspace-backpack": "^1.0.14",
"@blockly/zoom-to-fit": "^2.0.14",
"@fortawesome/fontawesome-svg-core": "^1.2.30", "@fortawesome/fontawesome-svg-core": "^1.2.30",
"@fortawesome/free-solid-svg-icons": "^5.14.0", "@fortawesome/free-solid-svg-icons": "^5.14.0",
"@fortawesome/react-fontawesome": "^0.1.11", "@fortawesome/react-fontawesome": "^0.1.11",
"@material-ui/core": "^4.11.0", "@material-ui/core": "^4.11.0",
"@monaco-editor/react": "^4.3.1",
"@sentry/react": "^6.0.0", "@sentry/react": "^6.0.0",
"@sentry/tracing": "^6.0.0", "@sentry/tracing": "^6.0.0",
"@testing-library/jest-dom": "^5.16.1", "@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2", "@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^7.2.1", "@testing-library/user-event": "^7.2.1",
"axios": "^0.22.0", "axios": "^0.22.0",
"blockly": "^6.20210701.0", "blockly": "^7.20211209.4",
"file-saver": "^2.0.2", "file-saver": "^2.0.2",
"markdown-it": "^12.3.2", "markdown-it": "^12.3.2",
"mnemonic-id": "^3.2.7", "mnemonic-id": "^3.2.7",
"moment": "^2.28.0", "moment": "^2.28.0",
"prismjs": "^1.25.0", "prismjs": "^1.25.0",
"react": "^17.0.2", "react": "^17.0.2",
"react-cookie-consent": "^7.0.0", "react-cookie-consent": "^7.2.1",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-markdown": "^5.0.2", "react-markdown": "^5.0.2",
"react-markdown-editor-lite": "^1.3.2", "react-markdown-editor-lite": "^1.3.2",
"react-mde": "^11.5.0", "react-mde": "^11.5.0",
"react-redux": "^7.2.4", "react-redux": "^7.2.4",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "^4.0.3", "react-scripts": "^5.0.0",
"reactour": "^1.18.0", "reactour": "^1.18.7",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"styled-components": "^4.4.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": { "scripts": {
"start": "node_modules/react-scripts/bin/react-scripts.js start", "start": "node_modules/react-scripts/bin/react-scripts.js start",
@ -50,16 +57,9 @@
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"
}, },
"browserslist": { "browserslist": [
"production": [
">0.2%", ">0.2%",
"not dead", "not dead",
"not op_mini all" "not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
] ]
}
} }

View File

@ -1,51 +1,50 @@
.wrapper { .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; overflow: hidden;
display: block; display: block;
position: relative; position: relative;
padding-bottom: 60px; /* height of your footer + 30px*/ padding-bottom: 60px; /* height of your footer + 30px*/
} }
.tutorial img {
.tutorial img{
display: flex; display: flex;
align-items: center; align-items: center;
max-height: 40vH; max-height: 40vh;
max-width: 100%; max-width: 100%;
margin: auto; margin: auto;
} }
.news img{ .news img {
display: flex; display: flex;
align-items: center; align-items: center;
max-height: 40vH; max-height: 40vh;
max-width: 100%; max-width: 100%;
margin: auto; margin: auto;
} }
.tutorial blockquote{ .tutorial blockquote {
background: #f9f9f9; background: #f9f9f9;
border-left: 10px solid#4EAF47; border-left: 10px solid#4EAF47;
margin: 1.5em 10px; margin: 1.5em 10px;
padding: 0.5em 10px; padding: 0.5em 10px;
quotes: "\201C""\201D""\2018""\2019"; quotes: "\201C""\201D""\2018""\2019";
} }
blockquote:before { blockquote:before {
color:#4EAF47; color: #4eaf47;
content: open-quote; content: open-quote;
font-size: 4em; font-size: 4em;
line-height: 0.1em; line-height: 0.1em;
margin-right: 0.25em; margin-right: 0.25em;
vertical-align: -0.4em; vertical-align: -0.4em;
} }
blockquote p { blockquote p {
display: inline; display: inline;
} }
.overlay { .overlay {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }

View File

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

View File

@ -12,6 +12,7 @@ import "./generator/index";
import { ZoomToFitControl } from "@blockly/zoom-to-fit"; import { ZoomToFitControl } from "@blockly/zoom-to-fit";
import { initialXml } from "./initialXml.js"; import { initialXml } from "./initialXml.js";
import { getMaxInstances } from "./helpers/maxInstances"; import { getMaxInstances } from "./helpers/maxInstances";
import { Backpack } from "@blockly/workspace-backpack";
class BlocklyWindow extends Component { class BlocklyWindow extends Component {
constructor(props) { constructor(props) {
@ -35,6 +36,9 @@ class BlocklyWindow extends Component {
Blockly.svgResize(workspace); Blockly.svgResize(workspace);
const zoomToFit = new ZoomToFitControl(workspace); const zoomToFit = new ZoomToFitControl(workspace);
zoomToFit.init(); zoomToFit.init();
// Initialize plugin.
const backpack = new Backpack(workspace);
backpack.init();
} }
componentDidUpdate(props) { componentDidUpdate(props) {

View File

@ -2,10 +2,17 @@ import Blockly from "blockly";
const setVariableFunction = function (defaultValue) { const setVariableFunction = function (defaultValue) {
return function (block) { return function (block) {
const variableName = Blockly["Arduino"].nameDB_.getName( var id = block.getFieldValue("VAR");
block.getFieldValue("VAR"),
Blockly.Variables.NAME_TYPE 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( const variableValue = Blockly["Arduino"].valueToCode(
block, block,
"VALUE", "VALUE",
@ -16,32 +23,12 @@ const setVariableFunction = function (defaultValue) {
.getVariableMap() .getVariableMap()
.getAllVariables(); .getAllVariables();
const myVar = allVars.filter((v) => v.name === variableName)[0]; const myVar = allVars.filter((v) => v.name === variableName)[0];
console.log(myVar);
var code = ""; var code = "";
if (myVar !== undefined) {
switch (myVar.type) {
default:
Blockly.Arduino.variables_[variableName + myVar.type] = Blockly.Arduino.variables_[variableName + myVar.type] =
myVar.type + " " + myVar.name + ";\n"; myVar.type + " " + myVar.name + ";\n";
code = variableName + " = " + (variableValue || defaultValue) + ";\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;
} }
return code; return code;
}; };

View File

@ -0,0 +1,284 @@
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 [defaultValue, setDefaultValue] = useState(
localStorage.getItem("ArduinoCode")
? localStorage.getItem("ArduinoCode")
: `
#include <senseBoxIO.h> //needs to be always included
void setup () {
}
void loop() {
}`
);
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) => {
console.log(data);
if (data.code === "Internal Server Error") {
setProgress(false);
setOpen(true);
setError(data.message);
}
setProgress(false);
const result = data.data.id;
setId(result);
const filename = "sketch";
window.open(
`${process.env.REACT_APP_COMPILER_URL}/download?id=${result}&board=${process.env.REACT_APP_BOARD}&filename=${filename}`,
"_self"
);
})
.catch((err) => {
console.log(err);
});
};
const saveIno = () => {
var filename = "sketch";
var code = editorRef.current.getValue();
filename = `${filename}.ino`;
var blob = new Blob([code], { type: "text/plain;charset=utf-8" });
saveAs(blob, filename);
};
const openIno = async () => {
const [myFileHandle] = await window.showOpenFilePicker();
setFileHandle(myFileHandle);
const file = await myFileHandle.getFile();
const contents = await file.text();
setFileContent(contents);
};
const toggleDrawer = (anchor, open) => (event) => {
if (
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
setOpen(false);
};
const resetCode = () => {
const resetCode = `
#include <senseBoxIO.h> //needs to be always included
void setup () {
}
void loop() {
}`;
editorRef.current.setValue(resetCode);
};
const resetTimeout = (id, newID) => {
clearTimeout(id);
return newID;
};
const editValue = (value) => {
setTime(resetTimeout(time, setTimeout(saveValue, 400)));
setValue(value);
};
const saveValue = () => {
localStorage.setItem("ArduinoCode", value);
setAutoSave(true);
setTimeout(() => setAutoSave(false), 1000);
};
const getBlocklyCode = () => {
var code = store.getState().workspace.code.arduino;
editorRef.current.setValue(code);
};
return (
<div>
<Grid container spacing={2}>
<Drawer
anchor={"bottom"}
open={open}
onClose={toggleDrawer("bottom", false)}
>
<h2
style={{
color: "#4EAF47",
paddingLeft: "1rem",
paddingRight: "1rem",
}}
>
{Blockly.Msg.drawer_ideerror_head}
</h2>
<p
style={{
color: "#4EAF47",
paddingLeft: "1rem",
paddingRight: "1rem",
}}
>
{Blockly.Msg.drawer_ideerror_text}
</p>
<Divider style={{ backgroundColor: "white" }} />
<p
style={{
backgroundColor: "black",
color: "#E47128",
padding: "1rem",
}}
>
{" "}
{`${error}`}{" "}
</p>
</Drawer>
<Grid item lg={8}>
<div style={{ display: "flex", alignItems: "center" }}>
<h1>Code Editor</h1>
<SaveIcon loading={autoSave} />
</div>
<MonacoEditor
height="80vh"
onChange={(value) => {
editValue(value);
}}
defaultLanguage="cpp"
defaultValue={defaultValue}
value={fileContent}
onMount={(editor, monaco) => {
editorRef.current = editor;
}}
/>
</Grid>
<Grid item lg={4}>
<Button
style={{ padding: "1rem", margin: "1rem" }}
variant="contained"
color="primary"
onClick={() => compile()}
>
Kompilieren
</Button>
<Button
style={{ padding: "1rem", margin: "1rem" }}
variant="contained"
color="primary"
onClick={() => saveIno()}
>
Save Code
</Button>
<Button
style={{ padding: "1rem", margin: "1rem" }}
variant="contained"
color="primary"
onClick={() => openIno()}
>
Open Code
</Button>
<Button
style={{ padding: "1rem", margin: "1rem" }}
variant="contained"
color="primary"
onClick={() => setResetDialog(true)}
>
Reset Editor
</Button>
<Button
style={{ padding: "1rem", margin: "1rem" }}
variant="contained"
color="primary"
onClick={() => getBlocklyCode()}
>
getBlocklyCode
</Button>
<Sidebar />
<Dialog
style={{ zIndex: 9999999 }}
fullWidth
maxWidth={"sm"}
open={progress}
title={"Code wird kompiliert"}
content={""}
>
<div>
Dein Code wird nun kompiliert und anschließend auf deinen Computer
heruntergeladen
</div>
</Dialog>{" "}
<Dialog
open={resetDialog}
title={Blockly.Msg.resetDialog_headline}
content={Blockly.Msg.resetDialog_text}
onClose={() => {
setResetDialog(false);
}}
onClick={() => {
setResetDialog(false);
}}
button={Blockly.Msg.button_cancel}
>
{" "}
<div style={{ marginTop: "10px" }}>
<Button
variant="contained"
color="primary"
onClick={() => {
resetCode();
setResetDialog(false);
}}
>
Zurücksetzen
</Button>
</div>
</Dialog>
</Grid>
</Grid>
</div>
);
};
export default withRouter(CodeEditor);

View File

@ -0,0 +1,332 @@
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) => {
console.log(data);
if (data.code === "Internal Server Error") {
this.setState({
progress: false,
file: false,
open: true,
title: Blockly.Msg.compiledialog_headline,
content: Blockly.Msg.compiledialog_text,
error: data.message,
});
}
this.setState({ id: data.data.id }, () => {
this.createFileName();
});
})
.catch((err) => {
console.log(err);
//this.setState({ progress: false, file: false, open: true, title: Blockly.Msg.compiledialog_headline, content: Blockly.Msg.compiledialog_text });
});
};
download = () => {
const id = this.state.id;
const filename = detectWhitespacesAndReturnReadableResult(this.state.name);
this.toggleDialog();
this.props.workspaceName(this.state.name);
window.open(
`${process.env.REACT_APP_COMPILER_URL}/download?id=${id}&board=${process.env.REACT_APP_BOARD}&filename=${filename}`,
"_self"
);
this.setState({ progress: false });
};
toggleDialog = () => {
this.setState({ open: !this.state, progress: false, appDialog: false });
};
createFileName = () => {
if (this.props.platform === true) {
const filename = detectWhitespacesAndReturnReadableResult(
this.state.name
);
this.setState({
link: `blocklyconnect-app://sketch/${filename}/${this.state.id}`,
});
this.setState({ appDialog: true });
} else {
if (this.state.name) {
this.download();
} else {
this.setState({
file: true,
open: true,
title: "Projekt kompilieren",
content:
"Bitte gib einen Namen für die Bennenung des zu kompilierenden Programms ein und bestätige diesen mit einem Klick auf 'Eingabe'.",
});
}
}
// if (this.state.name) {
// this.download();
// } else {
// this.setState({
// file: true,
// open: true,
// title: "Projekt kompilieren",
// content:
// "Bitte gib einen Namen für die Bennenung des zu kompilierenden Programms ein und bestätige diesen mit einem Klick auf 'Eingabe'.",
// });
// }
};
setFileName = (e) => {
this.setState({ name: e.target.value });
};
toggleDrawer = (anchor, open) => (event) => {
if (
event.type === "keydown" &&
(event.key === "Tab" || event.key === "Shift")
) {
return;
}
this.setState({ open: false });
};
render() {
return (
<div style={{}}>
{this.props.iconButton ? (
<Tooltip
title={Blockly.Msg.tooltip_compile_code}
arrow
style={{ marginRight: "5px" }}
>
<IconButton
className={`compileBlocks ${this.props.classes.iconButton}`}
onClick={() => this.compile()}
>
<FontAwesomeIcon icon={faClipboardCheck} size="m" />
</IconButton>
</Tooltip>
) : (
<Button
style={{ float: "right", color: "white" }}
variant="contained"
className={this.props.classes.button}
onClick={() => this.compile()}
>
<FontAwesomeIcon
icon={faClipboardCheck}
style={{ marginRight: "5px" }}
/>{" "}
Kompilieren
</Button>
)}
{this.props.platform === false ? (
<Backdrop
className={this.props.classes.backdrop}
open={this.state.progress}
>
<div className="overlay">
<img src={Copy} width="400" alt="copyimage"></img>
<h2>{Blockly.Msg.compile_overlay_head}</h2>
<p>{Blockly.Msg.compile_overlay_text}</p>
<p>
{Blockly.Msg.compile_overlay_help}
<a href="/faq" target="_blank">
FAQ
</a>
</p>
<CircularProgress color="inherit" />
</div>
</Backdrop>
) : (
<Backdrop
className={this.props.classes.backdrop}
open={this.state.progress}
>
<div className="overlay">
{/* <img src={Copy} width="400" alt="copyimage"></img> */}
<h2>Dein Code wird kompiliert!</h2>
<p>übertrage ihn anschließend mithlfe der senseBoxConnect-App</p>
<p>
{Blockly.Msg.compile_overlay_help}
<a href="/faq" target="_blank">
FAQ
</a>
</p>
<CircularProgress color="inherit" />
</div>
</Backdrop>
)}
<Drawer
anchor={"bottom"}
open={this.state.open}
onClose={this.toggleDrawer("bottom", false)}
>
<h2
style={{
color: "#4EAF47",
paddingLeft: "1rem",
paddingRight: "1rem",
}}
>
{Blockly.Msg.drawer_ideerror_head}
</h2>
<p
style={{
color: "#4EAF47",
paddingLeft: "1rem",
paddingRight: "1rem",
}}
>
{Blockly.Msg.drawer_ideerror_text}
</p>
<Divider style={{ backgroundColor: "white" }} />
<p
style={{
backgroundColor: "black",
color: "#E47128",
padding: "1rem",
}}
>
{" "}
{`${this.state.error}`}{" "}
</p>
</Drawer>
<Dialog
style={{ zIndex: 9999999 }}
fullWidth
maxWidth={"sm"}
open={this.state.appDialog}
title=""
content={""}
onClose={this.toggleDialog}
onClick={this.toggleDialog}
button={Blockly.Msg.button_close}
>
<div>
<p>Dein Code wurde erfolgreich kompiliert</p>
<a href={this.state.link}>
<Button
style={{ color: "white" }}
variant="contained"
className={this.props.classes.button}
onClick={() => this.toggleDialog()}
>
<FontAwesomeIcon
icon={faClipboardCheck}
style={{ marginRight: "5px" }}
/>{" "}
Starte Übertragung
</Button>
</a>
</div>
</Dialog>
</div>
);
}
}
Compile.propTypes = {
arduino: PropTypes.string.isRequired,
name: PropTypes.string,
workspaceName: PropTypes.func.isRequired,
platform: PropTypes.bool.isRequired,
};
const mapStateToProps = (state) => ({
arduino: state.workspace.code.arduino,
name: state.workspace.name,
platform: state.general.platform,
});
export default connect(mapStateToProps, { workspaceName })(
withStyles(styles, { withTheme: true })(Compile)
);

View File

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

View File

@ -0,0 +1,92 @@
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);
console.log(text);
setSerialPortContent((prevContent) => [
...prevContent,
[new Date(), text],
]);
}
}
} catch (error) {
setSerialPortContent((prevContent) => [
...prevContent,
[new Date(), error],
]);
}
}
} catch (error) {
setSerialPortContent((prevContent) => [
...prevContent,
[new Date(), error],
]);
}
};
return (
<>
<div className="flex items-center">
<Button type="button" variant="outlined" onClick={() => connectPort()}>
Connect to senseBox
</Button>
<label className="m-4 text-gray-700 text-base font-semibold px-6 py-3 rounded-lg">
Show timestamps
<input
onChange={handleClick}
checked={checked}
type="checkbox"
className="mx-4"
/>
</label>
<Button
type="button"
variant="outlined"
onClick={() => setSerialPortContent([])}
>
Clear
</Button>
</div>
<div className="font-mono">
{serialPortContent.map((log) => {
return (
<p>
{checked && (
<span className="font-medium mr-4">{log[0].toISOString()}</span>
)}
<span>{log[1]}</span>
</p>
);
})}
</div>
</>
);
};
export default SerialMonitor;

View File

@ -0,0 +1,132 @@
import React, { useEffect } from "react";
import Blockly from "blockly";
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 Dialog from "../Dialog";
import SerialMonitor from "./SerialMonitor.js";
import axios from "axios";
const Sidebar = () => {
const [alert, setAlert] = React.useState(false);
const [examples, setExamples] = React.useState([]);
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 toggleDialog = () => {
setAlert(false);
};
return (
<div>
{"serial" in navigator ? (
<Accordion>
<AccordionSummary
expandIcon={""}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography>Serial Monitor</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
<SerialMonitor />
</Typography>
</AccordionDetails>
</Accordion>
) : null}
<Accordion>
<AccordionSummary
expandIcon={""}
aria-controls="panel1a-content"
id="panel1a-header"
>
<Typography>Beispiele</Typography>
</AccordionSummary>
<AccordionDetails>
<Typography>
{examples.map((object, i) => {
return (
<Button
style={{ padding: "1rem", margin: "1rem" }}
variant="contained"
color="primary"
onClick={() => loadCode(object.code)}
>
{object.name}
</Button>
);
})}
</Typography>
</AccordionDetails>
</Accordion>
<Accordion>
<AccordionSummary
expandIcon={""}
aria-controls="panel2a-content"
id="panel2a-header"
>
<Typography>Installierte Libraries</Typography>
</AccordionSummary>
<AccordionDetails
style={{ padding: 0, height: "60vH", backgroundColor: "white" }}
>
<Typography
style={{ overflow: "auto", width: "100%", padding: "1rem" }}
>
<p>
For Dokumentation take a look at the installed libraries and their
source
</p>
{LibraryVersions().map((object, i) => {
return (
<p>
<a href={object.link} target="_blank" rel="noreferrer">
{object.library} {object.version}
</a>
</p>
);
})}
</Typography>
</AccordionDetails>
<Dialog
style={{ zIndex: 9999999 }}
fullWidth
maxWidth={"sm"}
open={alert}
title={Blockly.Msg.tabletDialog_headline}
content={""}
onClose={() => toggleDialog()}
onClick={() => toggleDialog()}
button={Blockly.Msg.button_close}
>
<div>{Blockly.Msg.tabletDialog_text}</div>
<div>
{Blockly.Msg.tabletDialog_more}{" "}
<a href="https://sensebox.de/app" target="_blank" rel="noreferrer">
https://sensebox.de/app
</a>
</div>
</Dialog>
</Accordion>
</div>
);
};
export default Sidebar;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,9 +15,9 @@ class Instruction extends Component {
var areRequirements = step.requirements && step.requirements.length > 0; var areRequirements = step.requirements && step.requirements.length > 0;
return ( return (
<div> <div>
<Typography variant="h4" style={{ marginBottom: "5px" }}> {/* <Typography variant="h4" style={{ marginBottom: "5px" }}>
{step.headline} {step.headline}
</Typography> </Typography> */}
<Typography style={isHardware ? {} : { marginBottom: "5px" }}> <Typography style={isHardware ? {} : { marginBottom: "5px" }}>
<ReactMarkdown <ReactMarkdown
className={"tutorial"} className={"tutorial"}

View File

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

View File

@ -1,31 +1,31 @@
import React, { Component } from 'react'; import React, { Component } from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { 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 tutorials from '../../data/tutorials';
import { fade } from '@material-ui/core/styles/colorManipulator'; import { fade } from "@material-ui/core/styles/colorManipulator";
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from "@material-ui/core/styles";
import Typography from '@material-ui/core/Typography'; import Typography from "@material-ui/core/Typography";
import Tooltip from '@material-ui/core/Tooltip'; import Tooltip from "@material-ui/core/Tooltip";
import Button from '@material-ui/core/Button'; import Button from "@material-ui/core/Button";
import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const styles = (theme) => ({ const styles = (theme) => ({
stepper: { stepper: {
width: 'calc(100% - 40px)', width: "calc(100% - 40px)",
height: '40px', height: "40px",
borderRadius: '25px', borderRadius: "25px",
padding: '0 20px', padding: "0 20px",
margin: '20px 0', margin: "20px 0",
display: 'flex', display: "flex",
justifyContent: 'space-between' justifyContent: "space-between",
}, },
stepperSuccess: { stepperSuccess: {
backgroundColor: fade(theme.palette.primary.main, 0.6), backgroundColor: fade(theme.palette.primary.main, 0.6),
@ -37,73 +37,147 @@ const styles = (theme) => ({
backgroundColor: fade(theme.palette.secondary.main, 0.6), backgroundColor: fade(theme.palette.secondary.main, 0.6),
}, },
color: { color: {
backgroundColor: 'transparent ' backgroundColor: "transparent ",
}, },
iconDivSuccess: { iconDivSuccess: {
color: theme.palette.primary.main color: theme.palette.primary.main,
}, },
iconDivError: { iconDivError: {
color: theme.palette.error.dark color: theme.palette.error.dark,
} },
}); });
class StepperHorizontal extends Component { class StepperHorizontal extends Component {
render() { render() {
var tutorialId = this.props.tutorial._id; 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 tasks = status.tasks;
var error = tasks.filter(task => task.type === 'error').length > 0; var error = tasks.filter((task) => task.type === "error").length > 0;
var success = tasks.filter(task => task.type === 'success').length / tasks.length; var success =
var tutorialStatus = success === 1 ? 'Success' : error ? 'Error' : 'Other'; tasks.filter((task) => task.type === "success").length / tasks.length;
var tutorialStatus = success === 1 ? "Success" : error ? "Error" : "Other";
var title = this.props.tutorial.title; var title = this.props.tutorial.title;
var activeStep = this.props.activeStep;
return ( return (
<div style={{ position: 'relative' }}> <div style={{ position: "relative" }}>
{error || success > 0 ? {error || success > 0 ? (
<div style={{ zIndex: -1, width: error ? 'calc(100% - 40px)' : `calc(${success * 100}% - 40px)`, borderRadius: success === 1 || error ? '25px' : '25px 0 0 25px', position: 'absolute', margin: 0, left: 0 }} className={clsx(this.props.classes.stepper, error ? this.props.classes.stepperError : this.props.classes.stepperSuccess)}> <div
</div> style={{
: null} zIndex: -1,
{success < 1 && !error ? width: error
<div style={{ zIndex: -2, width: `calc(${(1 - success) * 100}% - 40px)`, borderRadius: success === 0 ? '25px' : '0px 25px 25px 0', position: 'absolute', margin: 0, right: 0 }} className={clsx(this.props.classes.stepper, this.props.classes.stepperOther)}> ? "calc(100% - 40px)"
</div> : `calc(${success * 100}% - 40px)`,
: null} borderRadius: success === 1 || error ? "25px" : "25px 0 0 25px",
position: "absolute",
margin: 0,
left: 0,
}}
className={clsx(
this.props.classes.stepper,
error
? this.props.classes.stepperError
: this.props.classes.stepperSuccess
)}
></div>
) : null}
{success < 1 && !error ? (
<div
style={{
zIndex: -2,
width: `calc(${(1 - success) * 100}% - 40px)`,
borderRadius: success === 0 ? "25px" : "0px 25px 25px 0",
position: "absolute",
margin: 0,
right: 0,
}}
className={clsx(
this.props.classes.stepper,
this.props.classes.stepperOther
)}
></div>
) : null}
<div className={this.props.classes.stepper}> <div className={this.props.classes.stepper}>
<Button <Button
disabled//={tutorialIndex === 0} disabled //={tutorialIndex === 0}
//onClick={() => { this.props.history.push(`/tutorial/${tutorials[tutorialIndex - 1].id}`) }} //onClick={() => { this.props.history.push(`/tutorial/${tutorials[tutorialIndex - 1].id}`) }}
> >
{'<'} {"<"}
</Button> </Button>
<Tooltip style={{ display: 'flex', width: 'calc(100% - 64px - 64px)', justifyContent: 'center' }} title={title} arrow> <Tooltip
style={{
display: "flex",
width: "calc(100% - 64px - 64px)",
justifyContent: "center",
}}
title={title}
arrow
>
<div> <div>
{tutorialStatus !== 'Other' ? <div className={tutorialStatus === 'Success' && success === 1 ? this.props.classes.iconDivSuccess : this.props.classes.iconDivError} style={{ margin: 'auto 10px auto 0' }}><FontAwesomeIcon className={this.props.classes.icon} icon={tutorialStatus === 'Success' ? faCheck : faTimes} /></div> : null} {tutorialStatus !== "Other" ? (
<Typography variant='body2' style={{ fontWeight: 'bold', fontSize: '1.75em', margin: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'rgba(0, 0, 0, 0.54)' }}>{title}</Typography> <div
className={
tutorialStatus === "Success" && success === 1
? this.props.classes.iconDivSuccess
: this.props.classes.iconDivError
}
style={{ margin: "auto 10px auto 0" }}
>
<FontAwesomeIcon
className={this.props.classes.icon}
icon={tutorialStatus === "Success" ? faCheck : faTimes}
/>
</div>
) : null}
<Typography
variant="body2"
style={{
fontWeight: "bold",
fontSize: "1.75em",
margin: 0,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
color: "rgba(0, 0, 0, 0.54)",
}}
>
{title}
{title !== this.props.tutorial.steps[activeStep].headline
? ` - ${this.props.tutorial.steps[activeStep].headline}`
: null}
</Typography>
</div> </div>
</Tooltip> </Tooltip>
<Button <Button
disabled//={tutorialIndex + 1 === tutorials.length} disabled //={tutorialIndex + 1 === tutorials.length}
//onClick={() => { this.props.history.push(`/tutorial/${tutorials[tutorialIndex + 1].id}`) }} //onClick={() => { this.props.history.push(`/tutorial/${tutorials[tutorialIndex + 1].id}`) }}
> >
{'>'} {">"}
</Button> </Button>
</div> </div>
</div> </div>
); );
}; }
} }
StepperHorizontal.propTypes = { StepperHorizontal.propTypes = {
status: PropTypes.array.isRequired, status: PropTypes.array.isRequired,
change: PropTypes.number.isRequired, change: PropTypes.number.isRequired,
currentTutorialIndex: 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, change: state.tutorial.change,
status: state.tutorial.status, status: state.tutorial.status,
currentTutorialIndex: state.tutorial.currentIndex, currentTutorialIndex: state.tutorial.currentIndex,
tutorial: state.tutorial.tutorials[0] activeStep: state.tutorial.activeStep,
tutorial: state.tutorial.tutorials[0],
}); });
export default connect(mapStateToProps, null)(withRouter(withStyles(styles, { withTheme: true })(StepperHorizontal))); export default connect(
mapStateToProps,
null
)(withRouter(withStyles(styles, { withTheme: true })(StepperHorizontal)));

View File

@ -0,0 +1,72 @@
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() {
console.log(this.props.xml);
}
componentDidUpdate(prevProps) {
if (prevProps.xml !== this.props.xml) {
this.editValue(this.props.xml);
}
}
render() {
return (
<div>
<SaveIcon loading={this.state.saved} autosave={this.props.autosave} />
</div>
);
}
}
AutoSave.propTypes = {
xml: PropTypes.string.isRequired,
name: PropTypes.string,
workspaceName: PropTypes.func.isRequired,
setAutosave: PropTypes.func.isRequired,
autosave: PropTypes.bool.isRequired,
};
const mapStateToProps = (state) => ({
auto: state.general.autosave,
xml: state.workspace.code.xml,
name: state.workspace.name,
});
export default connect(mapStateToProps, { workspaceName })(AutoSave);

View File

@ -16,10 +16,7 @@ import { faClipboardCheck } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as Blockly from "blockly/core"; import * as Blockly from "blockly/core";
import Copy from "../copy.svg"; 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 MuiDrawer from "@material-ui/core/Drawer";
import Dialog from "../Dialog"; import Dialog from "../Dialog";
@ -71,15 +68,12 @@ class Compile extends Component {
}; };
} }
componentDidMount() { componentDidMount() {}
Prism.highlightAll();
}
componentDidUpdate(props) { componentDidUpdate(props) {
if (props.name !== this.props.name) { if (props.name !== this.props.name) {
this.setState({ name: this.props.name }); this.setState({ name: this.props.name });
} }
Prism.highlightAll();
} }
compile = () => { compile = () => {
@ -196,7 +190,7 @@ class Compile extends Component {
className={`compileBlocks ${this.props.classes.iconButton}`} className={`compileBlocks ${this.props.classes.iconButton}`}
onClick={() => this.compile()} onClick={() => this.compile()}
> >
<FontAwesomeIcon icon={faClipboardCheck} size="l" /> <FontAwesomeIcon icon={faClipboardCheck} size="m" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
) : ( ) : (

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 763 KiB

After

Width:  |  Height:  |  Size: 229 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

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

@ -0,0 +1,102 @@
export const LibraryVersions = () => {
return [
{
version: "1.4.2",
library: "sensebox/SenseBoxMCU-Lib",
link: "https://github.com/sensebox/SenseBoxMCU-Lib",
},
{
version: "1.0.12",
library: "sparkfun/SparkFun_SCD30_Arduino_Library",
},
{ version: "1.2.3", library: "adafruit/Adafruit-GFX-Library" },
{
version: "2.1.2",
library: "adafruit/Adafruit_BME280_Library",
},
{
version: "2.1.0",
library: "adafruit/Adafruit_BMP280_Library",
},
{
version: "1.1.1",
library: "adafruit/Adafruit_BME680",
},
{
version: "2.0.1",
library: "adafruit/Adafruit_BMP3XX",
},
{
version: "2.0.0",
library: "adafruit/Adafruit_HDC1000_Library",
},
{
version: "1.7.1",
library: "adafruit/Adafruit_BusIO",
},
{
version: "1.0.6",
library: "adafruit/Adafruit_NeoPixel",
},
{
version: "1.1.2",
library: "adafruit/Adafruit_SSD1306",
},
{
version: "1.0.2",
library: "adafruit/Adafruit_Sensor",
},
{
version: "3.8.0",
library: "milesburton/Arduino-Temperature-Control-Library",
},
{
version: "1.5.0",
library: "arduino-libraries/ArduinoBearSSL",
},
{
version: "1.3.4",
library: "arduino-libraries/ArduinoECCX08",
},
{
version: "2.0.0",
library: "arduino-libraries/ArduinoECCX08", //todo
},
{
version: "0.7.1",
library: "cmaglie/FlashStorage",
},
{
version: "1.5.1",
library: "matthijskooijman/arduino-lmic",
},
{
version: "3.0.1",
library: "thesolarnomad/lora-serialization ",
},
{
version: "todo",
library: "TSL45xxx",
},
{
version: "2.3.4",
library: "teensy/td_libs_OneWire.html",
},
{
version: "1.0.0",
library: "watterott/Arduino-Libs/tree/master/RV8523",
},
{
version: "1.0.0",
library: "sensebox/SDS011-select-serial ",
},
{
version: "1.0.0",
library: "Lucas-Steinmann/SSD1306-Plot-Library",
},
{
version: "1.0.0",
library: "senseBoxIO",
},
];
};