Merge pull request #153 from sensebox/markdown_file_upload
file upload with multer
This commit is contained in:
commit
8fd9eb01ed
@ -23,6 +23,7 @@
|
|||||||
"axios": "^0.22.0",
|
"axios": "^0.22.0",
|
||||||
"blockly": "^7.20211209.4",
|
"blockly": "^7.20211209.4",
|
||||||
"file-saver": "^2.0.2",
|
"file-saver": "^2.0.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",
|
||||||
@ -30,6 +31,7 @@
|
|||||||
"react-cookie-consent": "^7.2.1",
|
"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-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",
|
||||||
@ -46,7 +48,7 @@
|
|||||||
"react-error-overlay": "6.0.9"
|
"react-error-overlay": "6.0.9"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "node_modules/react-scripts/bin/react-scripts.js start",
|
||||||
"dev": "set \"REACT_APP_BLOCKLY_API=http://localhost:8080\" && npm start",
|
"dev": "set \"REACT_APP_BLOCKLY_API=http://localhost:8080\" && npm start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
|
@ -9,11 +9,11 @@ import { loadUser } from "./actions/authActions";
|
|||||||
|
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
||||||
import { ThemeProvider, createMuiTheme } from "@material-ui/core/styles";
|
import { ThemeProvider, createTheme } from "@material-ui/core/styles";
|
||||||
|
|
||||||
import Content from "./components/Content";
|
import Content from "./components/Content";
|
||||||
|
|
||||||
const theme = createMuiTheme({
|
const theme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
primary: {
|
primary: {
|
||||||
main: "#4EAF47",
|
main: "#4EAF47",
|
||||||
|
@ -13,10 +13,10 @@ import SaveIcon from "./SaveIcon";
|
|||||||
import store from "../../store";
|
import store from "../../store";
|
||||||
|
|
||||||
const CodeEditor = (props) => {
|
const CodeEditor = (props) => {
|
||||||
const [fileHandle, setFileHandle] = useState();
|
//const [filehandle, setFileHandle] = useState();
|
||||||
const [fileContent, setFileContent] = useState("");
|
const [fileContent, setFileContent] = useState("");
|
||||||
const [progress, setProgress] = useState(false);
|
const [progress, setProgress] = useState(false);
|
||||||
const [id, setId] = useState("");
|
// const [id, setId] = useState("");
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const editorRef = useRef(null);
|
const editorRef = useRef(null);
|
||||||
@ -24,20 +24,6 @@ const CodeEditor = (props) => {
|
|||||||
const [time, setTime] = useState(null);
|
const [time, setTime] = useState(null);
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
const [resetDialog, setResetDialog] = useState(false);
|
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 = () => {
|
const compile = () => {
|
||||||
setProgress(true);
|
setProgress(true);
|
||||||
@ -60,7 +46,7 @@ void loop() {
|
|||||||
}
|
}
|
||||||
setProgress(false);
|
setProgress(false);
|
||||||
const result = data.data.id;
|
const result = data.data.id;
|
||||||
setId(result);
|
//setId(result);
|
||||||
const filename = "sketch";
|
const filename = "sketch";
|
||||||
window.open(
|
window.open(
|
||||||
`${process.env.REACT_APP_COMPILER_URL}/download?id=${result}&board=${process.env.REACT_APP_BOARD}&filename=${filename}`,
|
`${process.env.REACT_APP_COMPILER_URL}/download?id=${result}&board=${process.env.REACT_APP_BOARD}&filename=${filename}`,
|
||||||
@ -83,7 +69,7 @@ void loop() {
|
|||||||
|
|
||||||
const openIno = async () => {
|
const openIno = async () => {
|
||||||
const [myFileHandle] = await window.showOpenFilePicker();
|
const [myFileHandle] = await window.showOpenFilePicker();
|
||||||
setFileHandle(myFileHandle);
|
//setFileHandle(myFileHandle);
|
||||||
|
|
||||||
const file = await myFileHandle.getFile();
|
const file = await myFileHandle.getFile();
|
||||||
const contents = await file.text();
|
const contents = await file.text();
|
||||||
@ -187,7 +173,20 @@ void loop() {
|
|||||||
editValue(value);
|
editValue(value);
|
||||||
}}
|
}}
|
||||||
defaultLanguage="cpp"
|
defaultLanguage="cpp"
|
||||||
defaultValue={defaultValue}
|
defaultValue={
|
||||||
|
localStorage.getItem("ArduinoCode")
|
||||||
|
? localStorage.getItem("ArduinoCode")
|
||||||
|
: `
|
||||||
|
#include <senseBoxIO.h> //needs to be always included
|
||||||
|
|
||||||
|
void setup () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
|
||||||
|
}`
|
||||||
|
}
|
||||||
value={fileContent}
|
value={fileContent}
|
||||||
onMount={(editor, monaco) => {
|
onMount={(editor, monaco) => {
|
||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
|
@ -1,39 +1,36 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from "react";
|
||||||
|
|
||||||
import Breadcrumbs from './Breadcrumbs';
|
import Breadcrumbs from "./Breadcrumbs";
|
||||||
|
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from "react-router-dom";
|
||||||
|
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from "@material-ui/core/Button";
|
||||||
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 ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from "react-markdown";
|
||||||
import Container from '@material-ui/core/Container';
|
import Container from "@material-ui/core/Container";
|
||||||
import ExpansionPanel from '@material-ui/core/ExpansionPanel';
|
import ExpansionPanel from "@material-ui/core/ExpansionPanel";
|
||||||
import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary';
|
import ExpansionPanelSummary from "@material-ui/core/ExpansionPanelSummary";
|
||||||
import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails';
|
import ExpansionPanelDetails from "@material-ui/core/ExpansionPanelDetails";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faChevronDown } from "@fortawesome/free-solid-svg-icons";
|
import { faChevronDown } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FaqQuestions } from '../data/faq'
|
import { FaqQuestions } from "../data/faq";
|
||||||
|
|
||||||
|
|
||||||
class Faq extends Component {
|
class Faq extends Component {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
panel: '',
|
panel: "",
|
||||||
expanded: false
|
expanded: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
handleChange = (panel) => {
|
handleChange = (panel) => {
|
||||||
this.setState({ panel: this.state.panel === panel ? '' : panel });
|
this.setState({ panel: this.state.panel === panel ? "" : panel });
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
// Ensure that Blockly.setLocale is adopted in the component.
|
// Ensure that Blockly.setLocale is adopted in the component.
|
||||||
// Otherwise, the text will not be displayed until the next update of the component.
|
// Otherwise, the text will not be displayed until the next update of the component.
|
||||||
|
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0);
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,139 +38,63 @@ class Faq extends Component {
|
|||||||
const { panel } = this.state;
|
const { panel } = this.state;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Breadcrumbs content={[{ link: this.props.location.pathname, title: 'FAQ' }]} />
|
<Breadcrumbs
|
||||||
|
content={[{ link: this.props.location.pathname, title: "FAQ" }]}
|
||||||
|
/>
|
||||||
<Container fixed>
|
<Container fixed>
|
||||||
<div style={{ margin: '0px 24px 0px 24px' }}>
|
<div style={{ margin: "0px 24px 0px 24px" }}>
|
||||||
<h1>FAQ</h1>
|
<h1>FAQ</h1>
|
||||||
{FaqQuestions().map((object, i) => {
|
{FaqQuestions().map((object, i) => {
|
||||||
return (
|
return (
|
||||||
<ExpansionPanel expanded={panel === `panel${i}`} onChange={() => this.handleChange(`panel${i}`)}>
|
<ExpansionPanel
|
||||||
|
expanded={panel === `panel${i}`}
|
||||||
|
onChange={() => this.handleChange(`panel${i}`)}
|
||||||
|
>
|
||||||
<ExpansionPanelSummary
|
<ExpansionPanelSummary
|
||||||
expandIcon={
|
expandIcon={<FontAwesomeIcon icon={faChevronDown} />}
|
||||||
<FontAwesomeIcon icon={faChevronDown} />
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<Typography variant="h6">{object.question}</Typography>
|
<Typography variant="h6">{object.question}</Typography>
|
||||||
</ExpansionPanelSummary>
|
</ExpansionPanelSummary>
|
||||||
<ExpansionPanelDetails>
|
<ExpansionPanelDetails>
|
||||||
<Typography>
|
<Typography>
|
||||||
<ReactMarkdown className="news" allowDangerousHtml="true" children={object.answer}>
|
<ReactMarkdown
|
||||||
</ReactMarkdown>
|
className="news"
|
||||||
|
allowDangerousHtml="true"
|
||||||
|
children={object.answer}
|
||||||
|
></ReactMarkdown>
|
||||||
</Typography>
|
</Typography>
|
||||||
</ExpansionPanelDetails>
|
</ExpansionPanelDetails>
|
||||||
</ExpansionPanel>
|
</ExpansionPanel>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
{
|
{this.props.button ? (
|
||||||
this.props.button ?
|
|
||||||
<Button
|
<Button
|
||||||
style={{ marginTop: '20px' }}
|
style={{ marginTop: "20px" }}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => { this.props.history.push(this.props.button.link) }}
|
onClick={() => {
|
||||||
|
this.props.history.push(this.props.button.link);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{this.props.button.title}
|
{this.props.button.title}
|
||||||
</Button>
|
</Button>
|
||||||
:
|
) : (
|
||||||
<Button
|
<Button
|
||||||
style={{ marginTop: '20px' }}
|
style={{ marginTop: "20px" }}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => { this.props.history.push('/') }}
|
onClick={() => {
|
||||||
|
this.props.history.push("/");
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{Blockly.Msg.button_back}
|
{Blockly.Msg.button_back}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(Faq);
|
export default withRouter(Faq);
|
||||||
/*
|
|
||||||
<ExpansionPanel expanded={panel === 'panel1'} onChange={() => this.handleChange('panel1')}>
|
|
||||||
<ExpansionPanelSummary
|
|
||||||
expandIcon={
|
|
||||||
<FontAwesomeIcon icon={faChevronDown} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Typography variant="h6">{Blockly.Msg.faq_q1_question}</Typography>
|
|
||||||
</ExpansionPanelSummary>
|
|
||||||
<ExpansionPanelDetails>
|
|
||||||
<Typography>
|
|
||||||
<ReactMarkdown className="news" allowDangerousHtml="true" children={Blockly.Msg.faq_q1_answer}>
|
|
||||||
</ReactMarkdown>
|
|
||||||
</Typography>
|
|
||||||
</ExpansionPanelDetails>
|
|
||||||
</ExpansionPanel>
|
|
||||||
<ExpansionPanel expanded={panel === 'panel2'} onChange={() => this.handleChange('panel2')}>
|
|
||||||
<ExpansionPanelSummary
|
|
||||||
expandIcon={
|
|
||||||
<FontAwesomeIcon icon={faChevronDown} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Typography>Frage 2</Typography>
|
|
||||||
</ExpansionPanelSummary>
|
|
||||||
<ExpansionPanelDetails>
|
|
||||||
<Typography>
|
|
||||||
Donec placerat, lectus sed mattis semper, neque lectus feugiat lectus, varius pulvinar
|
|
||||||
diam eros in elit. Pellentesque convallis laoreet laoreet.
|
|
||||||
</Typography>
|
|
||||||
</ExpansionPanelDetails>
|
|
||||||
</ExpansionPanel>
|
|
||||||
<ExpansionPanel expanded={panel === 'panel3'} onChange={() => this.handleChange('panel3')}>
|
|
||||||
<ExpansionPanelSummary
|
|
||||||
expandIcon={
|
|
||||||
<FontAwesomeIcon icon={faChevronDown} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Typography>Frage 3</Typography>
|
|
||||||
</ExpansionPanelSummary>
|
|
||||||
<ExpansionPanelDetails>
|
|
||||||
<Typography>
|
|
||||||
Nunc vitae orci ultricies, auctor nunc in, volutpat nisl. Integer sit amet egestas eros,
|
|
||||||
vitae egestas augue. Duis vel est augue.
|
|
||||||
</Typography>
|
|
||||||
</ExpansionPanelDetails>
|
|
||||||
</ExpansionPanel>
|
|
||||||
<ExpansionPanel expanded={panel === 'panel4'} onChange={() => this.handleChange('panel4')}>
|
|
||||||
<ExpansionPanelSummary
|
|
||||||
expandIcon={
|
|
||||||
<FontAwesomeIcon icon={faChevronDown} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Typography>Frage 4</Typography>
|
|
||||||
</ExpansionPanelSummary>
|
|
||||||
<ExpansionPanelDetails>
|
|
||||||
<Typography>
|
|
||||||
Nunc vitae orci ultricies, auctor nunc in, volutpat nisl. Integer sit amet egestas eros,
|
|
||||||
vitae egestas augue. Duis vel est augue.
|
|
||||||
</Typography>
|
|
||||||
</ExpansionPanelDetails>
|
|
||||||
</ExpansionPanel>
|
|
||||||
*/
|
|
||||||
|
|
||||||
// {{
|
|
||||||
// this.props.button ?
|
|
||||||
// <Button
|
|
||||||
// style={{ marginTop: '20px' }}
|
|
||||||
// variant="contained"
|
|
||||||
// color="primary"
|
|
||||||
// onClick={() => { this.props.history.push(this.props.button.link) }}
|
|
||||||
// >
|
|
||||||
// {this.props.button.title}
|
|
||||||
// </Button>
|
|
||||||
// :
|
|
||||||
// <Button
|
|
||||||
// style={{ marginTop: '20px' }}
|
|
||||||
// variant="contained"
|
|
||||||
// color="primary"
|
|
||||||
// onClick={() => { this.props.history.push('/') }}
|
|
||||||
// >
|
|
||||||
// {Blockly.Msg.button_back}
|
|
||||||
// </Button>
|
|
||||||
// }}
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,44 +1,41 @@
|
|||||||
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 { Route, Redirect, withRouter } from 'react-router-dom';
|
|
||||||
|
|
||||||
|
import { Route, Redirect, withRouter } from "react-router-dom";
|
||||||
|
|
||||||
class PrivateRoute extends Component {
|
class PrivateRoute extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return !this.props.progress ? (
|
||||||
!this.props.progress ?
|
|
||||||
<Route
|
<Route
|
||||||
{...this.props.exact}
|
{...this.props.exact}
|
||||||
render={({ location }) =>
|
render={({ location }) =>
|
||||||
this.props.isAuthenticated ? (
|
this.props.isAuthenticated
|
||||||
this.props.children
|
? this.props.children
|
||||||
) : (()=>{
|
: (() => {
|
||||||
return (
|
return (
|
||||||
<Redirect
|
<Redirect
|
||||||
to={{
|
to={{
|
||||||
pathname: "/user/login",
|
pathname: "/user/login",
|
||||||
state: { from: location }
|
state: { from: location },
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
/> : null
|
/>
|
||||||
);
|
) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PrivateRoute.propTypes = {
|
PrivateRoute.propTypes = {
|
||||||
isAuthenticated: PropTypes.bool.isRequired,
|
isAuthenticated: PropTypes.bool,
|
||||||
progress: PropTypes.bool.isRequired
|
progress: PropTypes.bool.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = (state) => ({
|
||||||
isAuthenticated: state.auth.isAuthenticated,
|
isAuthenticated: state.auth.isAuthenticated,
|
||||||
progress: state.auth.progress
|
progress: state.auth.progress,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(withRouter(PrivateRoute));
|
export default connect(mapStateToProps, null)(withRouter(PrivateRoute));
|
||||||
|
@ -74,7 +74,7 @@ class Assessment extends Component {
|
|||||||
(task) => task._id === currentTask._id
|
(task) => task._id === currentTask._id
|
||||||
);
|
);
|
||||||
var statusTask = status.tasks[taskIndex];
|
var statusTask = status.tasks[taskIndex];
|
||||||
|
console.log(statusTask);
|
||||||
return (
|
return (
|
||||||
<div className="assessmentDiv" style={{ width: "100%" }}>
|
<div className="assessmentDiv" style={{ width: "100%" }}>
|
||||||
<div style={{ float: "right", height: "40px" }}>
|
<div style={{ float: "right", height: "40px" }}>
|
||||||
|
@ -283,21 +283,6 @@ class Builder extends Component {
|
|||||||
// optional
|
// optional
|
||||||
newTutorial.append(`steps[${i}][xml]`, step.xml);
|
newTutorial.append(`steps[${i}][xml]`, step.xml);
|
||||||
}
|
}
|
||||||
if (step.media) {
|
|
||||||
// optional
|
|
||||||
if (step.media.youtube) {
|
|
||||||
newTutorial.append(
|
|
||||||
`steps[${i}][media][youtube]`,
|
|
||||||
step.media.youtube
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (step.media.picture) {
|
|
||||||
newTutorial.append(
|
|
||||||
`steps[${i}][media][picture]`,
|
|
||||||
step.media.picture
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return newTutorial;
|
return newTutorial;
|
||||||
}
|
}
|
||||||
|
@ -1,107 +1,159 @@
|
|||||||
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, setError, deleteError } from '../../../actions/tutorialBuilderActions';
|
import {
|
||||||
|
changeContent,
|
||||||
|
setError,
|
||||||
|
deleteError,
|
||||||
|
} from "../../../actions/tutorialBuilderActions";
|
||||||
|
|
||||||
import hardware from '../../../data/hardware.json';
|
import hardware from "../../../data/hardware.json";
|
||||||
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from "@material-ui/core/styles";
|
||||||
import withWidth, { isWidthDown } from '@material-ui/core/withWidth';
|
import withWidth, { isWidthDown } from "@material-ui/core/withWidth";
|
||||||
import GridList from '@material-ui/core/GridList';
|
import ImageList from "@material-ui/core/ImageList";
|
||||||
import GridListTile from '@material-ui/core/GridListTile';
|
import ImageListTile from "@material-ui/core/ImageListItem";
|
||||||
import GridListTileBar from '@material-ui/core/GridListTileBar';
|
import ImageListTileBar from "@material-ui/core/ImageListItemBar";
|
||||||
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 * as Blockly from 'blockly'
|
import * as Blockly from "blockly";
|
||||||
|
|
||||||
|
const styles = (theme) => ({
|
||||||
const styles = theme => ({
|
multiImageListTile: {
|
||||||
multiGridListTile: {
|
|
||||||
background: theme.palette.primary.main,
|
background: theme.palette.primary.main,
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
height: '30px'
|
height: "30px",
|
||||||
},
|
},
|
||||||
multiGridListTileTitle: {
|
multiImageListTileTitle: {
|
||||||
color: theme.palette.text.primary
|
color: theme.palette.text.primary,
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
cursor: 'pointer',
|
cursor: "pointer",
|
||||||
'&:hover': {
|
"&:hover": {
|
||||||
width: 'calc(100% - 4px)',
|
width: "calc(100% - 4px)",
|
||||||
height: 'calc(100% - 4px)',
|
height: "calc(100% - 4px)",
|
||||||
border: `2px solid ${theme.palette.primary.main}`
|
border: `2px solid ${theme.palette.primary.main}`,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
active: {
|
active: {
|
||||||
cursor: 'pointer',
|
cursor: "pointer",
|
||||||
width: 'calc(100% - 4px)',
|
width: "calc(100% - 4px)",
|
||||||
height: 'calc(100% - 4px)',
|
height: "calc(100% - 4px)",
|
||||||
border: `2px solid ${theme.palette.primary.main}`
|
border: `2px solid ${theme.palette.primary.main}`,
|
||||||
},
|
},
|
||||||
errorColor: {
|
errorColor: {
|
||||||
color: theme.palette.error.dark,
|
color: theme.palette.error.dark,
|
||||||
lineHeight: 'initial',
|
lineHeight: "initial",
|
||||||
marginBottom: '10px'
|
marginBottom: "10px",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
class Requirements extends Component {
|
class Requirements extends Component {
|
||||||
|
|
||||||
onChange = (hardware) => {
|
onChange = (hardware) => {
|
||||||
var hardwareArray = this.props.value;
|
var hardwareArray = this.props.value;
|
||||||
if (hardwareArray.filter(value => value === hardware).length > 0) {
|
if (hardwareArray.filter((value) => value === hardware).length > 0) {
|
||||||
hardwareArray = hardwareArray.filter(value => value !== hardware);
|
hardwareArray = hardwareArray.filter((value) => value !== hardware);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
hardwareArray.push(hardware);
|
hardwareArray.push(hardware);
|
||||||
if (this.props.error) {
|
if (this.props.error) {
|
||||||
this.props.deleteError(this.props.index, 'hardware');
|
this.props.deleteError(this.props.index, "hardware");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.props.changeContent(hardwareArray, this.props.index, 'hardware');
|
this.props.changeContent(hardwareArray, this.props.index, "hardware");
|
||||||
if (hardwareArray.length === 0) {
|
if (hardwareArray.length === 0) {
|
||||||
this.props.setError(this.props.index, 'hardware');
|
this.props.setError(this.props.index, "hardware");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var cols = isWidthDown('md', this.props.width) ? isWidthDown('sm', this.props.width) ? isWidthDown('xs', this.props.width) ? 2 : 3 : 4 : 6;
|
var cols = isWidthDown("md", this.props.width)
|
||||||
|
? isWidthDown("sm", this.props.width)
|
||||||
|
? isWidthDown("xs", this.props.width)
|
||||||
|
? 2
|
||||||
|
: 3
|
||||||
|
: 4
|
||||||
|
: 6;
|
||||||
return (
|
return (
|
||||||
<div style={{ marginBottom: '10px', padding: '18.5px 14px', borderRadius: '25px', border: '1px solid lightgrey', width: 'calc(100% - 28px)' }}>
|
<div
|
||||||
<FormLabel style={{ color: 'black' }}>Hardware</FormLabel>
|
style={{
|
||||||
<FormHelperText style={this.props.error ? { lineHeight: 'initial', marginTop: '5px' } : { marginTop: '5px', lineHeight: 'initial', marginBottom: '10px' }}>{Blockly.Msg.builder_hardware_order}</FormHelperText>
|
marginBottom: "10px",
|
||||||
{this.props.error ? <FormHelperText className={this.props.classes.errorColor}>{Blockly.Msg.builder_hardware_helper}</FormHelperText> : null}
|
padding: "18.5px 14px",
|
||||||
<GridList cellHeight={100} cols={cols} spacing={10}>
|
borderRadius: "25px",
|
||||||
|
border: "1px solid lightgrey",
|
||||||
|
width: "calc(100% - 28px)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormLabel style={{ color: "black" }}>Hardware</FormLabel>
|
||||||
|
<FormHelperText
|
||||||
|
style={
|
||||||
|
this.props.error
|
||||||
|
? { lineHeight: "initial", marginTop: "5px" }
|
||||||
|
: {
|
||||||
|
marginTop: "5px",
|
||||||
|
lineHeight: "initial",
|
||||||
|
marginBottom: "10px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{Blockly.Msg.builder_hardware_order}
|
||||||
|
</FormHelperText>
|
||||||
|
{this.props.error ? (
|
||||||
|
<FormHelperText className={this.props.classes.errorColor}>
|
||||||
|
{Blockly.Msg.builder_hardware_helper}
|
||||||
|
</FormHelperText>
|
||||||
|
) : null}
|
||||||
|
<ImageList rowHeight={100} cols={cols} gap={10}>
|
||||||
{hardware.map((picture, i) => (
|
{hardware.map((picture, i) => (
|
||||||
<GridListTile key={i} onClick={() => this.onChange(picture.id)} classes={{ tile: this.props.value.filter(value => value === picture.id).length > 0 ? this.props.classes.active : this.props.classes.border }}>
|
<ImageListTile
|
||||||
<div style={{ margin: 'auto', width: 'max-content' }}>
|
key={i}
|
||||||
<img src={`/media/hardware/${picture.src}`} alt={picture.name} height={100} />
|
onClick={() => this.onChange(picture.id)}
|
||||||
|
classes={{
|
||||||
|
item:
|
||||||
|
this.props.value.filter((value) => value === picture.id)
|
||||||
|
.length > 0
|
||||||
|
? this.props.classes.active
|
||||||
|
: this.props.classes.border,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ margin: "auto", width: "max-content" }}>
|
||||||
|
<img
|
||||||
|
src={`/media/hardware/${picture.src}`}
|
||||||
|
alt={picture.name}
|
||||||
|
height={100}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<GridListTileBar
|
<ImageListTileBar
|
||||||
classes={{ root: this.props.classes.multiGridListTile }}
|
classes={{ root: this.props.classes.multiImageListTile }}
|
||||||
title={
|
title={
|
||||||
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }} className={this.props.classes.multiGridListTileTitle}>
|
<div
|
||||||
|
style={{ overflow: "hidden", textOverflow: "ellipsis" }}
|
||||||
|
className={this.props.classes.multiImageListTileTitle}
|
||||||
|
>
|
||||||
{picture.name}
|
{picture.name}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</GridListTile>
|
</ImageListTile>
|
||||||
))}
|
))}
|
||||||
</GridList>
|
</ImageList>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Requirements.propTypes = {
|
Requirements.propTypes = {
|
||||||
changeContent: PropTypes.func.isRequired,
|
changeContent: PropTypes.func.isRequired,
|
||||||
setError: PropTypes.func.isRequired,
|
setError: PropTypes.func.isRequired,
|
||||||
deleteError: PropTypes.func.isRequired,
|
deleteError: PropTypes.func.isRequired,
|
||||||
change: PropTypes.number.isRequired
|
change: PropTypes.number.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = (state) => ({
|
||||||
change: state.builder.change
|
change: state.builder.change,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, { changeContent, setError, deleteError })(withStyles(styles, { withTheme: true })(withWidth()(Requirements)));
|
export default connect(mapStateToProps, {
|
||||||
|
changeContent,
|
||||||
|
setError,
|
||||||
|
deleteError,
|
||||||
|
})(withStyles(styles, { withTheme: true })(withWidth()(Requirements)));
|
||||||
|
81
src/components/Tutorial/Builder/MarkdownEditor.js
Normal file
81
src/components/Tutorial/Builder/MarkdownEditor.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import {
|
||||||
|
tutorialTitle,
|
||||||
|
jsonString,
|
||||||
|
changeContent,
|
||||||
|
setError,
|
||||||
|
deleteError,
|
||||||
|
} from "../../../actions/tutorialBuilderActions";
|
||||||
|
|
||||||
|
import FormControl from "@material-ui/core/FormControl";
|
||||||
|
import MarkdownIt from "markdown-it";
|
||||||
|
import Editor from "react-markdown-editor-lite";
|
||||||
|
import "react-markdown-editor-lite/lib/index.css";
|
||||||
|
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const mdParser = new MarkdownIt(/* Markdown-it options */);
|
||||||
|
|
||||||
|
const MarkdownEditor = (props) => {
|
||||||
|
//const [value, setValue] = React.useState(props.value);
|
||||||
|
|
||||||
|
const mdEditor = React.useRef(null);
|
||||||
|
|
||||||
|
function handleChange({ html, text }) {
|
||||||
|
//setValue(text);
|
||||||
|
var value = text;
|
||||||
|
props.changeContent(value, props.index, props.property, props.property2);
|
||||||
|
if (value.replace(/\s/g, "") === "") {
|
||||||
|
props.setError(props.index, props.property);
|
||||||
|
} else {
|
||||||
|
props.deleteError(props.index, props.property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadImage(files) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("files", files);
|
||||||
|
axios({
|
||||||
|
method: "post",
|
||||||
|
url: `${process.env.REACT_APP_BLOCKLY_API}/upload/uploadImage`,
|
||||||
|
data: formData,
|
||||||
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
resolve(
|
||||||
|
`${process.env.REACT_APP_BLOCKLY_API}/upload/` + res.data.filename
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
reject(new Error("error"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl variant="outlined" fullWidth style={{ marginBottom: "10px" }}>
|
||||||
|
<Editor
|
||||||
|
ref={mdEditor}
|
||||||
|
style={{ height: "500px" }}
|
||||||
|
renderHTML={(text) => mdParser.render(text)}
|
||||||
|
onChange={handleChange}
|
||||||
|
//value={value}
|
||||||
|
id={props.property}
|
||||||
|
label={props.label}
|
||||||
|
property={props.property}
|
||||||
|
onImageUpload={uploadImage}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(null, {
|
||||||
|
tutorialTitle,
|
||||||
|
jsonString,
|
||||||
|
changeContent,
|
||||||
|
setError,
|
||||||
|
deleteError,
|
||||||
|
})(MarkdownEditor);
|
@ -1,82 +1,117 @@
|
|||||||
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 { addStep, removeStep, changeStepIndex } from '../../../actions/tutorialBuilderActions';
|
import {
|
||||||
|
addStep,
|
||||||
|
removeStep,
|
||||||
|
changeStepIndex,
|
||||||
|
} from "../../../actions/tutorialBuilderActions";
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from "clsx";
|
||||||
|
|
||||||
import Textfield from './Textfield';
|
import Textfield from "./Textfield";
|
||||||
import StepType from './StepType';
|
import StepType from "./StepType";
|
||||||
import BlocklyExample from './BlocklyExample';
|
import BlocklyExample from "./BlocklyExample";
|
||||||
import Requirements from './Requirements';
|
import Requirements from "./Requirements";
|
||||||
import Hardware from './Hardware';
|
import Hardware from "./Hardware";
|
||||||
import Media from './Media';
|
|
||||||
|
|
||||||
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 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 { faPlus, faAngleDoubleUp, faAngleDoubleDown, faTrash } from "@fortawesome/free-solid-svg-icons";
|
import {
|
||||||
|
faPlus,
|
||||||
|
faAngleDoubleUp,
|
||||||
|
faAngleDoubleDown,
|
||||||
|
faTrash,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import MarkdownEditor from "./MarkdownEditor";
|
||||||
|
|
||||||
const styles = (theme) => ({
|
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,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
backgroundColor: theme.palette.error.dark,
|
backgroundColor: theme.palette.error.dark,
|
||||||
color: theme.palette.error.contrastText,
|
color: theme.palette.error.contrastText,
|
||||||
'&:hover': {
|
"&:hover": {
|
||||||
backgroundColor: theme.palette.error.dark,
|
backgroundColor: theme.palette.error.dark,
|
||||||
color: theme.palette.error.contrastText,
|
color: theme.palette.error.contrastText,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
class Step extends Component {
|
class Step extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var index = this.props.index;
|
var index = this.props.index;
|
||||||
var steps = this.props.steps;
|
var steps = this.props.steps;
|
||||||
return (
|
return (
|
||||||
<div style={{borderRadius: '25px', border: '1px solid lightgrey', padding: '10px 14px 10px 10px', marginBottom: '20px'}}>
|
<div
|
||||||
<Typography variant='h6' style={{marginBottom: '10px', marginLeft: '4px'}}>Schritt {index+1}</Typography>
|
style={{
|
||||||
<div style={{display: 'flex', position: 'relative'}}>
|
borderRadius: "25px",
|
||||||
<div style={{width: '40px', marginRight: '10px', position: 'absolute', left: '4px', bottom: '10px'}}>
|
border: "1px solid lightgrey",
|
||||||
<Tooltip title='Schritt hinzufügen' arrow>
|
padding: "10px 14px 10px 10px",
|
||||||
|
marginBottom: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
style={{ marginBottom: "10px", marginLeft: "4px" }}
|
||||||
|
>
|
||||||
|
Schritt {index + 1}
|
||||||
|
</Typography>
|
||||||
|
<div style={{ display: "flex", position: "relative" }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: "40px",
|
||||||
|
marginRight: "10px",
|
||||||
|
position: "absolute",
|
||||||
|
left: "4px",
|
||||||
|
bottom: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip title="Schritt hinzufügen" arrow>
|
||||||
<IconButton
|
<IconButton
|
||||||
className={this.props.classes.button}
|
className={this.props.classes.button}
|
||||||
style={index === 0 ? {} : {marginBottom: '5px'}}
|
style={index === 0 ? {} : { marginBottom: "5px" }}
|
||||||
onClick={() => this.props.addStep(index + 1)}
|
onClick={() => this.props.addStep(index + 1)}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faPlus} size="xs" />
|
<FontAwesomeIcon icon={faPlus} size="xs" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{index !== 0 ?
|
{index !== 0 ? (
|
||||||
<div>
|
<div>
|
||||||
<Tooltip title={`Schritt ${index+1} nach oben schieben`} arrow>
|
<Tooltip
|
||||||
|
title={`Schritt ${index + 1} nach oben schieben`}
|
||||||
|
arrow
|
||||||
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={index < 2}
|
disabled={index < 2}
|
||||||
className={this.props.classes.button}
|
className={this.props.classes.button}
|
||||||
style={{marginBottom: '5px'}}
|
style={{ marginBottom: "5px" }}
|
||||||
onClick={() => this.props.changeStepIndex(index, index - 1)}
|
onClick={() => this.props.changeStepIndex(index, index - 1)}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faAngleDoubleUp} size="xs" />
|
<FontAwesomeIcon icon={faAngleDoubleUp} size="xs" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title={`Schritt ${index+1} nach unten schieben`} arrow>
|
<Tooltip
|
||||||
|
title={`Schritt ${index + 1} nach unten schieben`}
|
||||||
|
arrow
|
||||||
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={index === steps.length - 1}
|
disabled={index === steps.length - 1}
|
||||||
className={this.props.classes.button}
|
className={this.props.classes.button}
|
||||||
style={{marginBottom: '5px'}}
|
style={{ marginBottom: "5px" }}
|
||||||
onClick={() => this.props.changeStepIndex(index, index + 1)}
|
onClick={() => this.props.changeStepIndex(index, index + 1)}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faAngleDoubleDown} size="xs" />
|
<FontAwesomeIcon icon={faAngleDoubleDown} size="xs" />
|
||||||
@ -85,34 +120,75 @@ class Step extends Component {
|
|||||||
<Tooltip title={`Schritt ${index + 1} löschen`} arrow>
|
<Tooltip title={`Schritt ${index + 1} löschen`} arrow>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={index === 0}
|
disabled={index === 0}
|
||||||
className={clsx(this.props.classes.button, this.props.classes.delete)}
|
className={clsx(
|
||||||
|
this.props.classes.button,
|
||||||
|
this.props.classes.delete
|
||||||
|
)}
|
||||||
onClick={() => this.props.removeStep(index)}
|
onClick={() => this.props.removeStep(index)}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon icon={faTrash} size="xs" />
|
<FontAwesomeIcon icon={faTrash} size="xs" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
: null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div style={{width: '100%', marginLeft: '54px'}}>
|
<div style={{ width: "100%", marginLeft: "54px" }}>
|
||||||
<StepType value={this.props.step.type} index={index} />
|
<StepType value={this.props.step.type} index={index} />
|
||||||
<Textfield value={this.props.step.headline} property={'headline'} label={'Überschrift'} index={index} error={this.props.error.steps[index].headline} errorText={`Gib eine Überschrift für die ${this.props.step.type === 'task' ? 'Aufgabe' : 'Anleitung'} ein.`} />
|
<Textfield
|
||||||
<Textfield value={this.props.step.text} property={'text'} label={this.props.step.type === 'task' ? 'Aufgabenstellung' : 'Instruktionen'} index={index} multiline error={this.props.error.steps[index].text} errorText={`Gib Instruktionen für die ${this.props.step.type === 'task' ? 'Aufgabe' : 'Anleitung'} ein.`}/>
|
value={this.props.step.headline}
|
||||||
{index === 0 ?
|
property={"headline"}
|
||||||
|
label={"Überschrift"}
|
||||||
|
index={index}
|
||||||
|
error={this.props.error.steps[index].headline}
|
||||||
|
errorText={`Gib eine Überschrift für die ${
|
||||||
|
this.props.step.type === "task" ? "Aufgabe" : "Anleitung"
|
||||||
|
} ein.`}
|
||||||
|
/>
|
||||||
|
<MarkdownEditor
|
||||||
|
value={this.props.step.text}
|
||||||
|
property={"text"}
|
||||||
|
label={
|
||||||
|
this.props.step.type === "task"
|
||||||
|
? "Aufgabenstellung"
|
||||||
|
: "Instruktionen"
|
||||||
|
}
|
||||||
|
index={index}
|
||||||
|
multiline
|
||||||
|
error={this.props.error.steps[index].text}
|
||||||
|
errorText={`Gib Instruktionen für die ${
|
||||||
|
this.props.step.type === "task" ? "Aufgabe" : "Anleitung"
|
||||||
|
} ein.`}
|
||||||
|
/>
|
||||||
|
{index === 0 ? (
|
||||||
<div>
|
<div>
|
||||||
<Requirements value={this.props.step.requirements ? this.props.step.requirements : []} index={index}/>
|
<Requirements
|
||||||
<Hardware value={this.props.step.hardware ? this.props.step.hardware : []} index={index} error={this.props.error.steps[index].hardware}/>
|
value={
|
||||||
|
this.props.step.requirements
|
||||||
|
? this.props.step.requirements
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
index={index}
|
||||||
|
/>
|
||||||
|
<Hardware
|
||||||
|
value={
|
||||||
|
this.props.step.hardware ? this.props.step.hardware : []
|
||||||
|
}
|
||||||
|
index={index}
|
||||||
|
error={this.props.error.steps[index].hardware}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
: null}
|
) : null}
|
||||||
{this.props.step.type === 'instruction' ?
|
<BlocklyExample
|
||||||
<Media value={this.props.step.media} picture={this.props.step.media && this.props.step.media.picture} youtube={this.props.step.media && this.props.step.media.youtube} url={this.props.step.url} index={index} error={this.props.error.steps[index].media} />
|
value={this.props.step.xml}
|
||||||
: null}
|
index={index}
|
||||||
<BlocklyExample value={this.props.step.xml} index={index} task={this.props.step.type === 'task'} error={this.props.error.steps[index].xml ? true : false}/>
|
task={this.props.step.type === "task"}
|
||||||
|
error={this.props.error.steps[index].xml ? true : false}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Step.propTypes = {
|
Step.propTypes = {
|
||||||
@ -124,10 +200,14 @@ Step.propTypes = {
|
|||||||
error: PropTypes.object.isRequired,
|
error: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = (state) => ({
|
||||||
steps: state.builder.steps,
|
steps: state.builder.steps,
|
||||||
change: state.builder.change,
|
change: state.builder.change,
|
||||||
error: state.builder.error
|
error: state.builder.error,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, { addStep, removeStep, changeStepIndex })(withStyles(styles, {withTheme: true})(Step));
|
export default connect(mapStateToProps, {
|
||||||
|
addStep,
|
||||||
|
removeStep,
|
||||||
|
changeStepIndex,
|
||||||
|
})(withStyles(styles, { withTheme: true })(Step));
|
||||||
|
@ -31,14 +31,6 @@ const styles = (theme) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
class Textfield extends Component {
|
class Textfield extends Component {
|
||||||
componentDidMount() {
|
|
||||||
if (this.props.error) {
|
|
||||||
if (this.props.property !== "media") {
|
|
||||||
this.props.deleteError(this.props.index, this.props.property);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange = (e) => {
|
handleChange = (e) => {
|
||||||
var value = e.target.value;
|
var value = e.target.value;
|
||||||
if (this.props.property === "title") {
|
if (this.props.property === "title") {
|
||||||
|
@ -1,47 +1,45 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from "react";
|
||||||
|
|
||||||
import Dialog from '../Dialog';
|
import Dialog from "../Dialog";
|
||||||
|
|
||||||
import hardware from '../../data/hardware.json';
|
import hardware from "../../data/hardware.json";
|
||||||
|
|
||||||
import { withStyles } from '@material-ui/core/styles';
|
import { withStyles } from "@material-ui/core/styles";
|
||||||
import withWidth, { isWidthDown } from '@material-ui/core/withWidth';
|
import withWidth, { isWidthDown } from "@material-ui/core/withWidth";
|
||||||
import Link from '@material-ui/core/Link';
|
import Link from "@material-ui/core/Link";
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from "@material-ui/core/Typography";
|
||||||
import IconButton from '@material-ui/core/IconButton';
|
import IconButton from "@material-ui/core/IconButton";
|
||||||
import GridList from '@material-ui/core/GridList';
|
import ImageList from "@material-ui/core/ImageList";
|
||||||
import GridListTile from '@material-ui/core/GridListTile';
|
import ImageListTile from "@material-ui/core/ImageList";
|
||||||
import GridListTileBar from '@material-ui/core/GridListTileBar';
|
import ImageListTileBar from "@material-ui/core/ImageListItemBar";
|
||||||
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faExpandAlt } from "@fortawesome/free-solid-svg-icons";
|
import { faExpandAlt } from "@fortawesome/free-solid-svg-icons";
|
||||||
import * as Blockly from 'blockly'
|
import * as Blockly from "blockly";
|
||||||
const styles = theme => ({
|
const styles = (theme) => ({
|
||||||
expand: {
|
expand: {
|
||||||
'&:hover': {
|
"&:hover": {
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
},
|
},
|
||||||
'&:active': {
|
"&:active": {
|
||||||
color: theme.palette.primary.main,
|
color: theme.palette.primary.main,
|
||||||
},
|
},
|
||||||
color: theme.palette.text.primary
|
color: theme.palette.text.primary,
|
||||||
},
|
},
|
||||||
multiGridListTile: {
|
multiImageListTile: {
|
||||||
background: theme.palette.primary.main,
|
background: theme.palette.primary.main,
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
height: '30px'
|
height: "30px",
|
||||||
|
},
|
||||||
|
multiImageListTileTitle: {
|
||||||
|
color: theme.palette.text.primary,
|
||||||
},
|
},
|
||||||
multiGridListTileTitle: {
|
|
||||||
color: theme.palette.text.primary
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
class Hardware extends Component {
|
class Hardware extends Component {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
open: false,
|
open: false,
|
||||||
hardwareInfo: {}
|
hardwareInfo: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClickOpen = (hardwareInfo) => {
|
handleClickOpen = (hardwareInfo) => {
|
||||||
@ -53,36 +51,57 @@ class Hardware extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var cols = isWidthDown('md', this.props.width) ? isWidthDown('sm', this.props.width) ? isWidthDown('xs', this.props.width) ? 2 : 3 : 4 : 6;
|
var cols = isWidthDown("md", this.props.width)
|
||||||
|
? isWidthDown("sm", this.props.width)
|
||||||
|
? isWidthDown("xs", this.props.width)
|
||||||
|
? 2
|
||||||
|
: 3
|
||||||
|
: 4
|
||||||
|
: 6;
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: '10px', marginBottom: '5px' }}>
|
<div style={{ marginTop: "10px", marginBottom: "5px" }}>
|
||||||
<Typography>{Blockly.Msg.tutorials_hardware_head}</Typography>
|
<Typography>{Blockly.Msg.tutorials_hardware_head}</Typography>
|
||||||
|
|
||||||
<GridList cellHeight={100} cols={cols} spacing={10}>
|
<ImageList rowHeight={100} cols={cols} spacing={10}>
|
||||||
{this.props.picture.map((picture, i) => {
|
{this.props.picture.map((picture, i) => {
|
||||||
var hardwareInfo = hardware.filter(hardware => hardware.id === picture)[0];
|
var hardwareInfo = hardware.filter(
|
||||||
|
(hardware) => hardware.id === picture
|
||||||
|
)[0];
|
||||||
return (
|
return (
|
||||||
<GridListTile key={i}>
|
<ImageListTile key={i}>
|
||||||
<div style={{ margin: 'auto', width: 'max-content' }}>
|
<div style={{ margin: "auto", width: "max-content" }}>
|
||||||
<img src={`/media/hardware/${hardwareInfo.src}`} alt={hardwareInfo.name} height={100} style={{ cursor: 'pointer' }} onClick={() => this.handleClickOpen(hardwareInfo)} />
|
<img
|
||||||
|
src={`/media/hardware/${hardwareInfo.src}`}
|
||||||
|
alt={hardwareInfo.name}
|
||||||
|
height={100}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={() => this.handleClickOpen(hardwareInfo)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<GridListTileBar
|
<ImageListTileBar
|
||||||
classes={{ root: this.props.classes.multiGridListTile }}
|
classes={{ root: this.props.classes.multiImageListTile }}
|
||||||
title={
|
title={
|
||||||
<div style={{ overflow: 'hidden', textOverflow: 'ellipsis' }} className={this.props.classes.multiGridListTileTitle}>
|
<div
|
||||||
|
style={{ overflow: "hidden", textOverflow: "ellipsis" }}
|
||||||
|
className={this.props.classes.multiImageListTileTitle}
|
||||||
|
>
|
||||||
{hardwareInfo.name}
|
{hardwareInfo.name}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
actionIcon={
|
actionIcon={
|
||||||
<IconButton className={this.props.classes.expand} aria-label='Vollbild' onClick={() => this.handleClickOpen(hardwareInfo)}>
|
<IconButton
|
||||||
|
className={this.props.classes.expand}
|
||||||
|
aria-label="Vollbild"
|
||||||
|
onClick={() => this.handleClickOpen(hardwareInfo)}
|
||||||
|
>
|
||||||
<FontAwesomeIcon icon={faExpandAlt} size="xs" />
|
<FontAwesomeIcon icon={faExpandAlt} size="xs" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</GridListTile>
|
</ImageListTile>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</GridList>
|
</ImageList>
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
style={{ zIndex: 1500 }}
|
style={{ zIndex: 1500 }}
|
||||||
@ -94,14 +113,26 @@ class Hardware extends Component {
|
|||||||
button={Blockly.Msg.button_close}
|
button={Blockly.Msg.button_close}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<img src={`/media/hardware/${this.state.hardwareInfo.src}`} width="100%" alt={this.state.hardwareInfo.name} />
|
<img
|
||||||
{Blockly.Msg.tutorials_hardware_moreInformation} <Link rel="noreferrer" target="_blank" href={this.state.hardwareInfo.url} color="primary">{Blockly.Msg.tutorials_hardware_here}</Link>.
|
src={`/media/hardware/${this.state.hardwareInfo.src}`}
|
||||||
|
width="100%"
|
||||||
|
alt={this.state.hardwareInfo.name}
|
||||||
|
/>
|
||||||
|
{Blockly.Msg.tutorials_hardware_moreInformation}{" "}
|
||||||
|
<Link
|
||||||
|
rel="noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
href={this.state.hardwareInfo.url}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
{Blockly.Msg.tutorials_hardware_here}
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withWidth()(withStyles(styles, { withTheme: true })(Hardware));
|
export default withWidth()(withStyles(styles, { withTheme: true })(Hardware));
|
||||||
|
@ -235,7 +235,7 @@ WorkspaceName.propTypes = {
|
|||||||
workspaceName: PropTypes.func.isRequired,
|
workspaceName: PropTypes.func.isRequired,
|
||||||
setDescription: PropTypes.func.isRequired,
|
setDescription: PropTypes.func.isRequired,
|
||||||
updateProject: PropTypes.func.isRequired,
|
updateProject: PropTypes.func.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string,
|
||||||
description: PropTypes.string.isRequired,
|
description: PropTypes.string.isRequired,
|
||||||
message: PropTypes.object.isRequired,
|
message: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
18
src/index.js
18
src/index.js
@ -1,30 +1,26 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from "react-dom";
|
||||||
import './index.css';
|
import "./index.css";
|
||||||
import App from './App';
|
import App from "./App";
|
||||||
import * as serviceWorker from './serviceWorker';
|
import * as serviceWorker from "./serviceWorker";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
import { Integrations } from "@sentry/tracing";
|
import { Integrations } from "@sentry/tracing";
|
||||||
|
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: "https://ffe5d54461f64c46b4bed5d77c130d6f@o507523.ingest.sentry.io/5598758",
|
dsn: "https://ffe5d54461f64c46b4bed5d77c130d6f@o507523.ingest.sentry.io/5598758",
|
||||||
autoSessionTracking: true,
|
autoSessionTracking: true,
|
||||||
integrations: [
|
integrations: [new Integrations.BrowserTracing()],
|
||||||
new Integrations.BrowserTracing(),
|
|
||||||
],
|
|
||||||
|
|
||||||
// We recommend adjusting this value in production, or using tracesSampler
|
// We recommend adjusting this value in production, or using tracesSampler
|
||||||
// for finer control
|
// for finer control
|
||||||
tracesSampleRate: 1.0,
|
tracesSampleRate: 1.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById("root")
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
// If you want your app to work offline and load faster, you can change
|
||||||
|
Loading…
x
Reference in New Issue
Block a user