add block sharing
This commit is contained in:
		
							parent
							
								
									0cb7bba521
								
							
						
					
					
						commit
						e647d6e58e
					
				
							
								
								
									
										1
									
								
								.env
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								.env
									
									
									
									
									
								
							| @ -1,2 +1,3 @@ | |||||||
| REACT_APP_COMPILER_URL=https://compiler.sensebox.de | REACT_APP_COMPILER_URL=https://compiler.sensebox.de | ||||||
| REACT_APP_BOARD=sensebox-mcu | REACT_APP_BOARD=sensebox-mcu | ||||||
|  | REACT_APP_BLOCKLY_API=http://46.101.243.134:3000 | ||||||
|  | |||||||
							
								
								
									
										32
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										32
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -8720,6 +8720,11 @@ | |||||||
|         "minimist": "^1.2.5" |         "minimist": "^1.2.5" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "mnemonic-id": { | ||||||
|  |       "version": "3.2.7", | ||||||
|  |       "resolved": "https://registry.npmjs.org/mnemonic-id/-/mnemonic-id-3.2.7.tgz", | ||||||
|  |       "integrity": "sha512-kysx9gAGbvrzuFYxKkcRjnsg/NK61ovJOV4F1cHTRl9T5leg+bo6WI0pWIvOFh1Z/yDL0cjA5R3EEGPPLDv/XA==" | ||||||
|  |     }, | ||||||
|     "moment": { |     "moment": { | ||||||
|       "version": "2.29.0", |       "version": "2.29.0", | ||||||
|       "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.0.tgz", |       "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.0.tgz", | ||||||
| @ -11394,6 +11399,13 @@ | |||||||
|         "tough-cookie": "~2.5.0", |         "tough-cookie": "~2.5.0", | ||||||
|         "tunnel-agent": "^0.6.0", |         "tunnel-agent": "^0.6.0", | ||||||
|         "uuid": "^3.3.2" |         "uuid": "^3.3.2" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "uuid": { | ||||||
|  |           "version": "3.4.0", | ||||||
|  |           "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", | ||||||
|  |           "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "request-promise-core": { |     "request-promise-core": { | ||||||
| @ -12125,6 +12137,13 @@ | |||||||
|       "requires": { |       "requires": { | ||||||
|         "faye-websocket": "^0.10.0", |         "faye-websocket": "^0.10.0", | ||||||
|         "uuid": "^3.0.1" |         "uuid": "^3.0.1" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "uuid": { | ||||||
|  |           "version": "3.4.0", | ||||||
|  |           "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", | ||||||
|  |           "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "sockjs-client": { |     "sockjs-client": { | ||||||
| @ -13269,9 +13288,9 @@ | |||||||
|       "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" |       "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" | ||||||
|     }, |     }, | ||||||
|     "uuid": { |     "uuid": { | ||||||
|       "version": "3.4.0", |       "version": "8.3.1", | ||||||
|       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", |       "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", | ||||||
|       "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" |       "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==" | ||||||
|     }, |     }, | ||||||
|     "v8-compile-cache": { |     "v8-compile-cache": { | ||||||
|       "version": "2.1.1", |       "version": "2.1.1", | ||||||
| @ -13929,6 +13948,13 @@ | |||||||
|       "requires": { |       "requires": { | ||||||
|         "ansi-colors": "^3.0.0", |         "ansi-colors": "^3.0.0", | ||||||
|         "uuid": "^3.3.2" |         "uuid": "^3.3.2" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "uuid": { | ||||||
|  |           "version": "3.4.0", | ||||||
|  |           "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", | ||||||
|  |           "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "webpack-manifest-plugin": { |     "webpack-manifest-plugin": { | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ | |||||||
|     "@testing-library/user-event": "^7.2.1", |     "@testing-library/user-event": "^7.2.1", | ||||||
|     "blockly": "^3.20200924.0", |     "blockly": "^3.20200924.0", | ||||||
|     "file-saver": "^2.0.2", |     "file-saver": "^2.0.2", | ||||||
|  |     "mnemonic-id": "^3.2.7", | ||||||
|     "moment": "^2.28.0", |     "moment": "^2.28.0", | ||||||
|     "prismjs": "^1.20.0", |     "prismjs": "^1.20.0", | ||||||
|     "react": "^16.13.1", |     "react": "^16.13.1", | ||||||
| @ -23,7 +24,8 @@ | |||||||
|     "react-router-dom": "^5.2.0", |     "react-router-dom": "^5.2.0", | ||||||
|     "react-scripts": "3.4.1", |     "react-scripts": "3.4.1", | ||||||
|     "redux": "^4.0.5", |     "redux": "^4.0.5", | ||||||
|     "redux-thunk": "^2.3.0" |     "redux-thunk": "^2.3.0", | ||||||
|  |     "uuid": "^8.3.1" | ||||||
|   }, |   }, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "start": "react-scripts start", |     "start": "react-scripts start", | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ import clsx from 'clsx'; | |||||||
| 
 | 
 | ||||||
| import Breadcrumbs from '../Breadcrumbs'; | import Breadcrumbs from '../Breadcrumbs'; | ||||||
| 
 | 
 | ||||||
| import gallery from './gallery.json'; | // import gallery from './gallery.json';
 | ||||||
| // import tutorials from '../../data/tutorials.json';
 | // import tutorials from '../../data/tutorials.json';
 | ||||||
| 
 | 
 | ||||||
| import { Link } from 'react-router-dom'; | import { Link } from 'react-router-dom'; | ||||||
| @ -49,8 +49,24 @@ const styles = (theme) => ({ | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class GalleryHome extends Component { | class GalleryHome extends Component { | ||||||
| 
 | 
 | ||||||
|  |     state = { | ||||||
|  |         gallery: [] | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     componentDidMount() { | ||||||
|  |         console.log(process.env.REACT_APP_BLOCKLY_API) | ||||||
|  |         fetch(process.env.REACT_APP_BLOCKLY_API + this.props.location.pathname) | ||||||
|  |             .then(res => res.json()) | ||||||
|  |             .then((data) => { | ||||||
|  |                 this.setState({ gallery: data }) | ||||||
|  |             }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     render() { |     render() { | ||||||
|         return ( |         return ( | ||||||
|             <div> |             <div> | ||||||
| @ -58,7 +74,7 @@ class GalleryHome extends Component { | |||||||
| 
 | 
 | ||||||
|                 <h1>Gallery</h1> |                 <h1>Gallery</h1> | ||||||
|                 <Grid container spacing={2}> |                 <Grid container spacing={2}> | ||||||
|                     {gallery.map((gallery, i) => { |                     {this.state.gallery.map((gallery, i) => { | ||||||
|                         return ( |                         return ( | ||||||
|                             <Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}> |                             <Grid item xs={12} sm={6} md={4} xl={3} key={i} style={{}}> | ||||||
|                                 <Link to={`/gallery/${gallery.id}`} style={{ textDecoration: 'none', color: 'inherit' }}> |                                 <Link to={`/gallery/${gallery.id}`} style={{ textDecoration: 'none', color: 'inherit' }}> | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ import WorkspaceFunc from './WorkspaceFunc'; | |||||||
| import BlocklyWindow from './Blockly/BlocklyWindow'; | import BlocklyWindow from './Blockly/BlocklyWindow'; | ||||||
| import CodeViewer from './CodeViewer'; | import CodeViewer from './CodeViewer'; | ||||||
| import TrashcanButtons from './TrashcanButtons'; | import TrashcanButtons from './TrashcanButtons'; | ||||||
|  | import { createNameId } from 'mnemonic-id'; | ||||||
| 
 | 
 | ||||||
| import Grid from '@material-ui/core/Grid'; | import Grid from '@material-ui/core/Grid'; | ||||||
| import IconButton from '@material-ui/core/IconButton'; | import IconButton from '@material-ui/core/IconButton'; | ||||||
| @ -18,7 +19,6 @@ import { withStyles } from '@material-ui/core/styles'; | |||||||
| 
 | 
 | ||||||
| import { faCode } from "@fortawesome/free-solid-svg-icons"; | import { faCode } from "@fortawesome/free-solid-svg-icons"; | ||||||
| import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||
| import gallery from './Gallery/gallery.json'; |  | ||||||
| 
 | 
 | ||||||
| const styles = (theme) => ({ | const styles = (theme) => ({ | ||||||
|   codeOn: { |   codeOn: { | ||||||
| @ -46,11 +46,19 @@ class Home extends Component { | |||||||
| 
 | 
 | ||||||
|   state = { |   state = { | ||||||
|     codeOn: false, |     codeOn: false, | ||||||
|  |     gallery: [], | ||||||
|  |     share: [], | ||||||
|     projectToLoad: undefined |     projectToLoad: undefined | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentDidMount() { |   componentDidMount() { | ||||||
|     this.setState({ projectToLoad: gallery.find(project => project.id == this.props.match.params.galleryId) }) | 
 | ||||||
|  |     this.props.workspaceName(createNameId()); | ||||||
|  |     fetch(process.env.BLOCKLY_API + this.props.location.pathname) | ||||||
|  |       .then(res => res.json()) | ||||||
|  |       .then((data) => { | ||||||
|  |         this.setState({ projectToLoad: data }) | ||||||
|  |       }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -85,6 +93,7 @@ class Home extends Component { | |||||||
|     if (this.state.projectToLoad) { |     if (this.state.projectToLoad) { | ||||||
|       console.log(this.state.projectToLoad.xml) |       console.log(this.state.projectToLoad.xml) | ||||||
|     } |     } | ||||||
|  |     console.log(this.props); | ||||||
|     return ( |     return ( | ||||||
|       <div> |       <div> | ||||||
|         <div style={{ float: 'right', height: '40px', marginBottom: '20px' }}><WorkspaceFunc /></div> |         <div style={{ float: 'right', height: '40px', marginBottom: '20px' }}><WorkspaceFunc /></div> | ||||||
|  | |||||||
| @ -20,6 +20,7 @@ class Routes extends Component { | |||||||
|           <Route path="/tutorial" exact component={TutorialHome} /> |           <Route path="/tutorial" exact component={TutorialHome} /> | ||||||
|           <Route path="/gallery" exact component={GalleryHome} /> |           <Route path="/gallery" exact component={GalleryHome} /> | ||||||
|           <Route path="/gallery/:galleryId" exact component={Home} /> |           <Route path="/gallery/:galleryId" exact component={Home} /> | ||||||
|  |           <Route path="/share/:shareId" exact component={Home} /> | ||||||
|           <Route path="/tutorial/builder" exact component={Builder} /> |           <Route path="/tutorial/builder" exact component={Builder} /> | ||||||
|           <Route path="/tutorial/:tutorialId" exact component={Tutorial} /> |           <Route path="/tutorial/:tutorialId" exact component={Tutorial} /> | ||||||
|           <Route component={NotFound} /> |           <Route component={NotFound} /> | ||||||
|  | |||||||
| @ -12,7 +12,6 @@ import { initialXml } from './Blockly/initialXml.js'; | |||||||
| 
 | 
 | ||||||
| import Compile from './Compile'; | import Compile from './Compile'; | ||||||
| import SolutionCheck from './Tutorial/SolutionCheck'; | import SolutionCheck from './Tutorial/SolutionCheck'; | ||||||
| import Dialog from './Dialog'; |  | ||||||
| import Snackbar from './Snackbar'; | import Snackbar from './Snackbar'; | ||||||
| 
 | 
 | ||||||
| import withWidth, { isWidthDown } from '@material-ui/core/withWidth'; | import withWidth, { isWidthDown } from '@material-ui/core/withWidth'; | ||||||
| @ -22,8 +21,19 @@ import IconButton from '@material-ui/core/IconButton'; | |||||||
| 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 { createId } from 'mnemonic-id'; | ||||||
| 
 | 
 | ||||||
| import { faPen, faSave, faUpload, faCamera, faShare } from "@fortawesome/free-solid-svg-icons"; | 
 | ||||||
|  | import Dialog from './Dialog'; | ||||||
|  | // import Dialog from '@material-ui/core/Dialog';
 | ||||||
|  | import DialogActions from '@material-ui/core/DialogActions'; | ||||||
|  | import DialogContent from '@material-ui/core/DialogContent'; | ||||||
|  | import DialogContentText from '@material-ui/core/DialogContentText'; | ||||||
|  | import DialogTitle from '@material-ui/core/DialogTitle'; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | import { faPen, faSave, faUpload, faCamera, faShare, faShareAlt } from "@fortawesome/free-solid-svg-icons"; | ||||||
| import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||
| 
 | 
 | ||||||
| const styles = (theme) => ({ | const styles = (theme) => ({ | ||||||
| @ -49,6 +59,7 @@ const styles = (theme) => ({ | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| class WorkspaceFunc extends Component { | class WorkspaceFunc extends Component { | ||||||
| 
 | 
 | ||||||
|   constructor(props) { |   constructor(props) { | ||||||
| @ -60,13 +71,17 @@ class WorkspaceFunc extends Component { | |||||||
|       open: false, |       open: false, | ||||||
|       file: false, |       file: false, | ||||||
|       saveFile: false, |       saveFile: false, | ||||||
|  |       share: false, | ||||||
|       name: props.name, |       name: props.name, | ||||||
|       snackbar: false, |       snackbar: false, | ||||||
|       key: '', |       key: '', | ||||||
|       message: '' |       message: '', | ||||||
|  |       id: '' | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|   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 }); | ||||||
| @ -74,7 +89,7 @@ class WorkspaceFunc extends Component { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   toggleDialog = () => { |   toggleDialog = () => { | ||||||
|     this.setState({ open: !this.state }); |     this.setState({ open: !this.state, share: false }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   saveXmlFile = () => { |   saveXmlFile = () => { | ||||||
| @ -87,6 +102,41 @@ class WorkspaceFunc extends Component { | |||||||
|     saveAs(blob, fileName); |     saveAs(blob, fileName); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   shareBlocks = () => { | ||||||
|  |     let code = this.props.xml; | ||||||
|  |     let requestOptions = ''; | ||||||
|  |     let id = ''; | ||||||
|  |     if (this.state.id !== '') { | ||||||
|  |       requestOptions = { | ||||||
|  |         method: 'PUT', | ||||||
|  |         headers: { 'Content-Type': 'application/json' }, | ||||||
|  |         body: JSON.stringify({ | ||||||
|  |           id: this.state.id, | ||||||
|  |           name: this.state.name, | ||||||
|  |           xml: code | ||||||
|  |         }) | ||||||
|  |       }; | ||||||
|  |       fetch(process.env.BLOCKLY_API + '/share' + this.state.id, requestOptions) | ||||||
|  |         .then(response => response.json()) | ||||||
|  |         .then(data => this.setState({ share: true })); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |       id = createId(10); | ||||||
|  |       requestOptions = { | ||||||
|  |         method: 'POST', | ||||||
|  |         headers: { 'Content-Type': 'application/json' }, | ||||||
|  |         body: JSON.stringify({ | ||||||
|  |           id: id, | ||||||
|  |           name: this.state.name, | ||||||
|  |           xml: code | ||||||
|  |         }) | ||||||
|  |       }; | ||||||
|  |       fetch(process.env.BLOCKLY_API + '/share', requestOptions) | ||||||
|  |         .then(response => response.json()) | ||||||
|  |         .then(data => this.setState({ id: data.id, share: true })); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   getSvg = () => { |   getSvg = () => { | ||||||
|     const workspace = Blockly.getMainWorkspace(); |     const workspace = Blockly.getMainWorkspace(); | ||||||
|     var canvas = workspace.svgBlockCanvas_.cloneNode(true); |     var canvas = workspace.svgBlockCanvas_.cloneNode(true); | ||||||
| @ -195,6 +245,8 @@ class WorkspaceFunc extends Component { | |||||||
|     this.setState({ snackbar: true, key: Date.now(), message: 'Das Projekt wurde erfolgreich zurückgesetzt.' }); |     this.setState({ snackbar: true, key: Date.now(), message: 'Das Projekt wurde erfolgreich zurückgesetzt.' }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|   render() { |   render() { | ||||||
|     return ( |     return ( | ||||||
|       <div style={{ width: 'max-content', display: 'flex' }}> |       <div style={{ width: 'max-content', display: 'flex' }}> | ||||||
| @ -227,9 +279,11 @@ class WorkspaceFunc extends Component { | |||||||
|           /> |           /> | ||||||
|           <label htmlFor="open-blocks"> |           <label htmlFor="open-blocks"> | ||||||
|             <Tooltip title='Blöcke öffnen' arrow style={{ marginRight: '5px' }}> |             <Tooltip title='Blöcke öffnen' arrow style={{ marginRight: '5px' }}> | ||||||
|               <div className={this.props.classes.button} style={{borderRadius: '50%', cursor: 'pointer', display: 'table-cell', |               <div className={this.props.classes.button} style={{ | ||||||
|  |                 borderRadius: '50%', cursor: 'pointer', display: 'table-cell', | ||||||
|                 verticalAlign: 'middle', |                 verticalAlign: 'middle', | ||||||
|               textAlign: 'center'}}> |                 textAlign: 'center' | ||||||
|  |               }}> | ||||||
|                 <FontAwesomeIcon icon={faUpload} style={{ width: '18px', height: '18px' }} /> |                 <FontAwesomeIcon icon={faUpload} style={{ width: '18px', height: '18px' }} /> | ||||||
|               </div> |               </div> | ||||||
|             </Tooltip> |             </Tooltip> | ||||||
| @ -251,6 +305,37 @@ class WorkspaceFunc extends Component { | |||||||
|             <FontAwesomeIcon icon={faShare} size="xs" flip='horizontal' /> |             <FontAwesomeIcon icon={faShare} size="xs" flip='horizontal' /> | ||||||
|           </IconButton> |           </IconButton> | ||||||
|         </Tooltip> |         </Tooltip> | ||||||
|  |         <Tooltip title='Blöcke teilen' arrow> | ||||||
|  |           <IconButton | ||||||
|  |             className={this.props.classes.button} | ||||||
|  |             onClick={() => this.shareBlocks()} | ||||||
|  |           > | ||||||
|  |             <FontAwesomeIcon icon={faShareAlt} size="xs" flip='horizontal' /> | ||||||
|  |           </IconButton> | ||||||
|  |         </Tooltip> | ||||||
|  | 
 | ||||||
|  |         <Dialog open={this.state.share} onClose={this.toggleDialog} aria-labelledby="form-dialog-title"> | ||||||
|  |           <DialogTitle id="form-dialog-title">Dein Link wurde erstellt.</DialogTitle> | ||||||
|  |           <DialogContent> | ||||||
|  |             <DialogContentText> | ||||||
|  |               Über den folgenden Link kannst du dein Programm teilen. | ||||||
|  |           </DialogContentText> | ||||||
|  |             <TextField | ||||||
|  |               autoFocus | ||||||
|  |               margin="dense" | ||||||
|  |               id="name" | ||||||
|  |               defaultValue={"http://localhost:3000/share/" + this.state.id} | ||||||
|  |               label="url" | ||||||
|  |               type="email" | ||||||
|  |               fullWidth | ||||||
|  |             /> | ||||||
|  |           </DialogContent> | ||||||
|  |           <DialogActions> | ||||||
|  |             <Button onClick={this.toggleDialog} color="primary"> | ||||||
|  |               Cancel | ||||||
|  |           </Button> | ||||||
|  |           </DialogActions> | ||||||
|  |         </Dialog> | ||||||
| 
 | 
 | ||||||
|         <Dialog |         <Dialog | ||||||
|           open={this.state.open} |           open={this.state.open} | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user