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