file upload with multer
This commit is contained in:
parent
72a88e4a1c
commit
c908aa5b63
@ -21,6 +21,7 @@
|
|||||||
"axios": "^0.22.0",
|
"axios": "^0.22.0",
|
||||||
"blockly": "^6.20210701.0",
|
"blockly": "^6.20210701.0",
|
||||||
"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",
|
||||||
@ -28,6 +29,7 @@
|
|||||||
"react-cookie-consent": "^7.0.0",
|
"react-cookie-consent": "^7.0.0",
|
||||||
"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",
|
||||||
@ -39,7 +41,7 @@
|
|||||||
"uuid": "^8.3.1"
|
"uuid": "^8.3.1"
|
||||||
},
|
},
|
||||||
"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",
|
||||||
|
@ -41,6 +41,8 @@ import FormControl from "@material-ui/core/FormControl";
|
|||||||
import Select from "@material-ui/core/Select";
|
import Select from "@material-ui/core/Select";
|
||||||
import * as Blockly from "blockly";
|
import * as Blockly from "blockly";
|
||||||
|
|
||||||
|
import MarkdownEditor from "./MarkdownEditor";
|
||||||
|
|
||||||
const styles = (theme) => ({
|
const styles = (theme) => ({
|
||||||
backdrop: {
|
backdrop: {
|
||||||
zIndex: theme.zIndex.drawer + 1,
|
zIndex: theme.zIndex.drawer + 1,
|
||||||
|
83
src/components/Tutorial/Builder/MarkdownEditor.js
Normal file
83
src/components/Tutorial/Builder/MarkdownEditor.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import React, { Component, useRef } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import {
|
||||||
|
tutorialTitle,
|
||||||
|
jsonString,
|
||||||
|
changeContent,
|
||||||
|
setError,
|
||||||
|
deleteError,
|
||||||
|
} from "../../../actions/tutorialBuilderActions";
|
||||||
|
|
||||||
|
import FormControl from "@material-ui/core/FormControl";
|
||||||
|
import Button from "@material-ui/core/Button";
|
||||||
|
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;
|
||||||
|
console.log(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}
|
||||||
|
plugins={[]}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(null, {
|
||||||
|
tutorialTitle,
|
||||||
|
jsonString,
|
||||||
|
changeContent,
|
||||||
|
setError,
|
||||||
|
deleteError,
|
||||||
|
})(MarkdownEditor);
|
@ -20,6 +20,8 @@ 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,
|
||||||
@ -47,67 +49,88 @@ class Step extends Component {
|
|||||||
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 style={{ borderRadius: '25px', border: '1px solid lightgrey', padding: '10px 14px 10px 10px', marginBottom: '20px' }}>
|
||||||
<Typography variant='h6' style={{marginBottom: '10px', marginLeft: '4px'}}>Schritt {index+1}</Typography>
|
<Typography variant='h6' style={{ marginBottom: '10px', marginLeft: '4px' }}>Schritt {index + 1}</Typography>
|
||||||
<div style={{display: 'flex', position: 'relative'}}>
|
<div style={{ display: 'flex', position: 'relative' }}>
|
||||||
<div style={{width: '40px', marginRight: '10px', position: 'absolute', left: '4px', bottom: '10px'}}>
|
<div style={{ width: '40px', marginRight: '10px', position: 'absolute', left: '4px', bottom: '10px' }}>
|
||||||
<Tooltip title='Schritt hinzufügen' arrow>
|
<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" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<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}
|
||||||
|
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 ?
|
{index === 0 ?
|
||||||
<div>
|
<div>
|
||||||
<Requirements value={this.props.step.requirements ? this.props.step.requirements : []} index={index}/>
|
<Requirements value={this.props.step.requirements ? this.props.step.requirements : []} index={index} />
|
||||||
<Hardware value={this.props.step.hardware ? this.props.step.hardware : []} index={index} error={this.props.error.steps[index].hardware}/>
|
<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' ?
|
{this.props.step.type === 'instruction' ?
|
||||||
<Media value={this.props.step.media} picture={this.props.step.media && this.props.step.media.picture} youtube={this.props.step.media && this.props.step.media.youtube} url={this.props.step.url} index={index} error={this.props.error.steps[index].media} />
|
<Media value={this.props.step.media} picture={this.props.step.media && this.props.step.media.picture} youtube={this.props.step.media && this.props.step.media.youtube} url={this.props.step.url} index={index} error={this.props.error.steps[index].media} />
|
||||||
: null}
|
: null}
|
||||||
<BlocklyExample value={this.props.step.xml} index={index} task={this.props.step.type === 'task'} error={this.props.error.steps[index].xml ? true : false}/>
|
<BlocklyExample value={this.props.step.xml} index={index} task={this.props.step.type === 'task'} error={this.props.error.steps[index].xml ? true : false} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -130,4 +153,4 @@ const mapStateToProps = state => ({
|
|||||||
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));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user