From 29fa3967256d88581fdbe5d8800f01bb2250cef7 Mon Sep 17 00:00:00 2001 From: Bhargava Reddy Date: Mon, 22 Jul 2019 18:47:55 +0530 Subject: [PATCH] Adding TextIDE Component --- client/app/components/Pages/CodeEditor.js | 52 ++++ .../components/common/TextIDE/RunButton.js | 52 ++++ .../components/common/TextIDE/SubmitButton.js | 68 ++++ .../app/components/common/TextIDE/TextIDE.js | 294 ++++++++++++++++++ .../app/components/common/TextIDE/Toolbar.js | 219 +++++++++++++ client/app/components/common/TextIDE/index.js | 1 + .../common/TextIDE/sampleFunction.js | 23 ++ client/app/index.js | 3 + config/webpack.common.js | 7 +- package.json | 3 + 10 files changed, 721 insertions(+), 1 deletion(-) create mode 100644 client/app/components/Pages/CodeEditor.js create mode 100644 client/app/components/common/TextIDE/RunButton.js create mode 100644 client/app/components/common/TextIDE/SubmitButton.js create mode 100644 client/app/components/common/TextIDE/TextIDE.js create mode 100644 client/app/components/common/TextIDE/Toolbar.js create mode 100644 client/app/components/common/TextIDE/index.js create mode 100644 client/app/components/common/TextIDE/sampleFunction.js diff --git a/client/app/components/Pages/CodeEditor.js b/client/app/components/Pages/CodeEditor.js new file mode 100644 index 0000000..bad7004 --- /dev/null +++ b/client/app/components/Pages/CodeEditor.js @@ -0,0 +1,52 @@ +import React, { Component } from 'react' +import TextIDE from "../common/TextIDE"; +import sampleFunction from '../common/TextIDE/sampleFunction'; + +export default class CodeEditor extends Component { + render() { + return ( +
+ +
+ + ) + } +} + +/* +DEFAULT VALUES FOR + width= "700px" + height= "600px" + languages = ['C','C++','Python 2','Python 3','Java'] + defaultCode = '' for all languages defined above + fontSize = '12pt' + fontSizes = {['12pt','14pt','16pt']} + theme = 'vs' + readOnly = {false} + showToolbar= {true} + showCustomInput = {true} + showRunButton = {true} + showSubmitButton = {true} + runFunction = NO DEFAULTS + submitFunction = NO DEFAULTS +*/ \ No newline at end of file diff --git a/client/app/components/common/TextIDE/RunButton.js b/client/app/components/common/TextIDE/RunButton.js new file mode 100644 index 0000000..54d5df0 --- /dev/null +++ b/client/app/components/common/TextIDE/RunButton.js @@ -0,0 +1,52 @@ +import React,{Component} from 'react'; +import {Button} from 'reactstrap'; + + +class RunCodeButton extends Component { + + constructor(props){ + super(props); + + this.submitCode = this.submitCode.bind(this); + } + + submitCode(event) { + event.preventDefault() + + const codeText = this.props.editor.getValue() + const codeLanguage = this.props.language.toLowerCase() + const isCustomInput = this.props.isCustomInput + var stdin = '' + + if(isCustomInput){ + stdin = document.getElementById('customInput').value + } + + var data = { + content: codeText, + language: codeLanguage, + customInput: isCustomInput, + stdin: stdin, + submit: false + } + if(this.props.customFunction === undefined){ + console.error('No Run Function is defined for the TextIDE Component') + } + else { + this.props.customFunction(data) + } + } + + render(){ + return ( +
+ +
+ ) + } + +} + +export default RunCodeButton; \ No newline at end of file diff --git a/client/app/components/common/TextIDE/SubmitButton.js b/client/app/components/common/TextIDE/SubmitButton.js new file mode 100644 index 0000000..55f5387 --- /dev/null +++ b/client/app/components/common/TextIDE/SubmitButton.js @@ -0,0 +1,68 @@ +import React,{Component} from 'react' +import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap' + +class SubmitCodeButton extends Component { + + constructor(props){ + super(props); + this.state = { + showConfirmation: false, + } + this.submitCode = this.submitCode.bind(this); + this.toggleConfirmation = this.toggleConfirmation.bind(this); + } + + toggleConfirmation(){ + this.setState(prevState => ({ + showConfirmation: !prevState.showConfirmation + })) + } + + submitCode(event) { + event.preventDefault() + this.toggleConfirmation() + + const codeText = this.props.editor.getValue() + const codeLanguage = this.props.language.toLowerCase() + + var data = { + content: codeText, + language: codeLanguage, + customInput: false, + stdin: '', + submit: true + } + + if(this.props.customFunction === undefined){ + console.error('No Submit Function is defined for the TextIDE Component') + } + else { + this.props.customFunction(data) + } + } + + render(){ + return ( + +
+ + + + Confirm to Submit + + Are you sure you want to submit? + + + + + + +
+ ) + } + +} + +export default SubmitCodeButton; \ No newline at end of file diff --git a/client/app/components/common/TextIDE/TextIDE.js b/client/app/components/common/TextIDE/TextIDE.js new file mode 100644 index 0000000..f4127bd --- /dev/null +++ b/client/app/components/common/TextIDE/TextIDE.js @@ -0,0 +1,294 @@ +import React from 'react'; +import Toolbar from './Toolbar'; +import RunCodeButton from './RunButton'; +import SubmitCodeButton from './SubmitButton'; +import MonacoEditor from 'react-monaco-editor'; +import { InputGroup, InputGroupText, Input } from 'reactstrap'; + + + +const buttonRowStyle ={ + marginBottom: '5px', + marginTop :'5px' + +} + +const bottomRowStyle ={ + overflow: 'auto', + marginTop: '15px', + marginBottom: '5px', + padding: '0px', +} + +const editorStyle ={ + padding:'0px', + boxShadow: '0px 3px 5px black' +} + +// Add(in the same format) or remove languages + +const languages = { + 'C' : 'c', + 'C++' : 'cpp', + 'Python 2' : 'python', + 'Python 3' : 'python', + 'Java' : 'java' +} + +//No other themes available +const themes = { + 'Light' : 'vs', + 'Dark' : 'vs-dark', + 'High Contrast' : 'hc-black' +} + +// Add(in the same format) or remove fontSizes +// First fontSize is the default fontSize +const fontSizes = ['12pt','14pt','16pt'] + +class TextIDE extends React.Component{ + + constructor(props){ + super(props); + this.customLanguages = this.props.languages===undefined? Object.keys(languages):this.props.languages + this.languages = Object() + + for (const i in this.customLanguages) { + if (this.customLanguages[i] in languages){ + this.languages[this.customLanguages[i]] = languages[this.customLanguages[i]] + } + else{ + var lang_name = this.customLanguages[i].charAt(0).toUpperCase() + this.customLanguages[i].toLowerCase().slice(1) + var lang_name_lowercase = this.customLanguages[i].toLowerCase() + this.languages[lang_name] = lang_name_lowercase + } + } + + this.defaultWidth = '700px' + this.defaultHeight = '600px' + if(this.props.width===undefined){ + this.width = this.defaultWidth + }else{ + if(this.props.width.indexOf('px')>-1) + this.width = this.props.width + else + this.width = parseInt(this.props.width)+'px' + } + if(this.props.height===undefined){ + this.height = this.defaultHeight + }else{ + if(this.props.height.indexOf('px')>-1) + this.height = this.props.height + else + this.height = parseInt(this.props.height)+'px' + } + + this.defaultCode = Object() + for (const lang in this.props.defaultCode){ + var lang_name = lang.charAt(0).toUpperCase() + lang.toLowerCase().slice(1) + this.defaultCode[lang_name] = this.props.defaultCode[lang] + } + + for(const lang in this.languages){ + if (!(lang in this.defaultCode)){ + this.defaultCode[lang] = '' + } + } + + this.fontSize = this.props.fontSize===undefined? fontSizes[0]:this.props.fontSize + this.fontSizes = this.props.fontSizes===undefined? fontSizes:this.props.fontSizes + this.theme = this.props.theme===undefined? Object.values(themes)[0]:this.props.theme + + this.readOnly = this.props.readOnly===undefined? false:this.props.readOnly + //this.showButtons = this.props.showButtons===undefined ? (!this.readOnly):(this.props.showButtons && !this.readOnly) + this.showRunButton = this.props.showRunButton===undefined ? (!this.readOnly): (this.props.showRunButton && !this.readOnly) + this.showSubmitButton = this.props.showSubmitButton===undefined ? (!this.readOnly): (this.props.showSubmitButton && !this.readOnly) + + this.state = { + width: this.width, + height: this.height, + editorTheme: this.theme, + editorLanguage: Object.values(this.languages)[0], + editorLanguageKey: Object.keys(this.languages)[0], + editorFontSize: this.fontSize, + editorText: this.defaultCode[Object.keys(this.languages)[0]], + languages: this.languages, + themes: themes, + fontSizes: this.fontSizes, + customInput: false, + readOnly: this.readOnly, + showToolbar: this.props.showToolbar===undefined ? (!this.readOnly):(this.props.showToolbar && !this.readOnly), + //showButtons: this.showButtons, + showRunButton: this.showRunButton, + showSubmitButton: this.showSubmitButton, + showCustomInputOption: this.props.showCustomInput===undefined ? (!this.readOnly && (this.showRunButton || this.showSubmitButton)): + (this.props.showCustomInput && !this.readOnly && (this.showRunButton || this.showSubmitButton)), + + } + + this.changeEditorBackground = this.changeEditorBackground.bind(this) + this.changeEditorLanguage = this.changeEditorLanguage.bind(this) + this.changeEditorFontSize = this.changeEditorFontSize.bind(this) + this.editorDidMount = this.editorDidMount.bind(this) + this.showCustomInput = this.showCustomInput.bind(this) + this.resetCode = this.resetCode.bind(this) + + } + + changeEditorBackground(event,theme){ + this.setState({ + editorTheme: theme + }) + } + + changeEditorLanguage(event,language,languageKey){ + this.setState({ + editorLanguage: language, + editorLanguageKey: languageKey, + editorText: this.defaultCode[languageKey] + }) + this.state.editor.getModel().setValue(this.defaultCode[languageKey]) + } + + changeEditorFontSize(event,fontsize){ + this.setState({ + editorFontSize: fontsize + }) + } + + showCustomInput(event){ + this.setState( prevState => ({ + customInput: !prevState.customInput + })) + } + + editorDidMount(editor) { + this.setState({ + editor: editor + }); + editor.focus() + } + + //Reset Code to defualt value + resetCode() { + this.state.editor.getModel().setValue(this.defaultCode[this.state.editorLanguageKey]) + this.state.editor.focus() + } + + componentDidMount() { + //Check if runFunction and submitFunction have been defined as the props.. + if(!this.readOnly){ + if(this.showRunButton && this.props.runFunction === undefined){ + console.warn('Run Function is not defined for the TextIDE\nPass a function as a prop(runFunction) to the TextIDE component') + } + if(this.showSubmitButton && this.props.submitFunction === undefined){ + console.warn('Submit Function is not defined for the TextIDE\nPass a function as a prop(submitFunction) to the TextIDE component') + } + } + window.addEventListener("resize", this.updateDimensions.bind(this)); + } + + //Auto-resizing of the editor + updateDimensions() { + this.state.editor.layout(); + } + + componentWillUnmount() { + window.removeEventListener("resize", this.updateDimensions.bind(this)); + } + + render() { + + const containerStyle = { + padding: '15px 15px 2px 15px', + maxWidth: this.state.width + } + + const editorOptions= { + fontSize: this.state.editorFontSize, + value: this.state.editorText, + readOnly: this.state.readOnly, + snippetSuggestions: true, + autoClosingBrackets: true, + autoClosingQuotes: true, + quickSuggestions: true, + copyWithSyntaxHighlighting: true, + formatOnPaste: true, + autoIndent: true, + } + + + return( +
+ + {(this.state.showToolbar) && ( +
+ +
+ )} + +
+ +
+ + {( +
+ {(this.state.showCustomInputOption) && ( +
+ + + +   Test against custom input + + + {(this.state.customInput) && ( +
+
+ + +
+ )} +
+ )} +
+ {(this.state.showRunButton) && ( +
+ +
+ )} + {(this.state.showSubmitButton) && ( +
+ +
+ )} +
+
+ )} + +
+ ); + } +} + +export default TextIDE; \ No newline at end of file diff --git a/client/app/components/common/TextIDE/Toolbar.js b/client/app/components/common/TextIDE/Toolbar.js new file mode 100644 index 0000000..9dbd7d3 --- /dev/null +++ b/client/app/components/common/TextIDE/Toolbar.js @@ -0,0 +1,219 @@ +import React,{Component} from "react"; +import { FaCaretDown, FaCog, FaUndoAlt } from "react-icons/fa"; +import { Button, ButtonGroup, UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; + + + +const toolBarStyle = { + height: '40px', + background:'#DCDCDC', + marginBottom: '0px', + boxShadow: '0px 3px 5px black' +} + +const dropdownStyle = { + width: '120px', + height:'30px', + background: '#DCDCDC', + color: 'black', + margin: '5px', + boxShadow: 'none' +} + +const dropdownButtonStyle = { + border: '1px black solid', + height:'28px', + width: '120px', + padding: '0px' +} + +const editorSettingsStyle = { + maxWidth: '500px', + background: '#F5F5F5', + borderRadius: '5px', + paddingLeft: '2px', + paddingRight: '2px', + marginTop: '10px' +} + +const editorSettingsRowStyle = { + padding: '10px 0 10px 0', +} + +const settingButton = { + margin: '2px', + borderRadius: '2px', + width:'100px', + fontSize:'10pt', +} + +const labelStyle = { + margin:'auto 10px auto 10px', + width:'100px', + fontSize: '12pt', + fontFamily: 'Monospace, Courier New' +} + +const dropdownBoxStyle = { + minWidth:'120px', + maxWidth: '140px', + maxHeight:'150px', + overflowY: 'auto', +} + +class Toolbar extends Component{ + + constructor(props){ + super(props); + this.state = { + chosenLanguage: this.props.editorLanguage, + chosenLanguageShow: this.props.editorLanguageKey, + languages: this.props.languages, + themes: this.props.themes, + fontSizes: this.props.fontSizes + } + this.changeEditorLanguage = this.changeEditorLanguage.bind(this) + this.changeEditorBackground = this.changeEditorBackground.bind(this) + this.changeEditorFontSize = this.changeEditorFontSize.bind(this) + this.resetCode = this.resetCode.bind(this) + + } + + changeEditorBackground(event) { + event.preventDefault(); + this.props.changeEditorBackground(event,event.target.id); + } + + changeEditorLanguage(event) { + event.preventDefault(); + this.props.changeEditorLanguage(event,event.target.id,event.target.value); + this.setState({ + chosenLanguage: event.target.id, + chosenLanguageShow: event.target.value + }); + } + + changeEditorFontSize(event) { + event.preventDefault(); + this.props.changeEditorFontSize(event,event.target.id); + } + + //Reset Code to defualt value + resetCode() { + this.props.resetCode() + } + + + render(){ + const lang_items = [] + for(const lang in this.state.languages){ + var lang_button = + {lang} + + lang_items.push(lang_button); + } + + const theme_items = [] + for (const theme_name in this.state.themes) { + const btnclass = this.props.editorTheme === this.state.themes[theme_name]?'btn btn-sm btn-success':'btn btn-sm' + var theme_button =
+ +
+ theme_items.push(theme_button) + } + + const font_items = [] + for (const font_size in this.state.fontSizes) { + const btnclass = this.props.editorFontSize === this.state.fontSizes[font_size]?'btn btn-sm btn-success':'btn btn-sm' + var font_button =
+ +
+ font_items.push(font_button) + } + + return( + +
+
+
+ +
+
+ + + {this.state.chosenLanguageShow} + + + {lang_items} + + +
+
+
+ + + +
+
+ + + + + + + +
+
+
+ +
+ + {theme_items} + +
+
+
+ +
+ + {font_items} + +
+
+
+
+
+ +
+ + {theme_items} + +
+
+
+ +
+ + {font_items} + +
+
+
+
+ +
+
+
+
+
+ +
+ ) + } +} + +export default Toolbar \ No newline at end of file diff --git a/client/app/components/common/TextIDE/index.js b/client/app/components/common/TextIDE/index.js new file mode 100644 index 0000000..ed115c6 --- /dev/null +++ b/client/app/components/common/TextIDE/index.js @@ -0,0 +1 @@ +export { default } from "./TextIDE"; \ No newline at end of file diff --git a/client/app/components/common/TextIDE/sampleFunction.js b/client/app/components/common/TextIDE/sampleFunction.js new file mode 100644 index 0000000..9c8a041 --- /dev/null +++ b/client/app/components/common/TextIDE/sampleFunction.js @@ -0,0 +1,23 @@ +// This is a sample function to send as prop to the runFunction or submitFunction + +/** + * + * @param {Object} data Object that will be passed to your function (runFunction or submitFunction) + * @param {string} data.content Code from the editor + * @param {string} data.language Language used + * @param {boolean} data.customInput TRUE if the user selected custom input and FALSE otherwise + * @param {string} data.stdin stdin for the submission if customInput is TRUE and '' if customInput is FALSE + * @param {boolean} data.submit TRUE if user wants to submit code and FALSE if user wants to run code + */ + +export default function sampleFunction(data) { + + console.log( + 'Content: '+ data['content'], + '\nLanguage: '+ data['language'], + '\nCustomInput: '+ data['customInput'], + '\nstdin: ' + data['stdin'], + '\nSubmit Code: ' + data['submit'] + ) + +} \ No newline at end of file diff --git a/client/app/index.js b/client/app/index.js index e7e1fb5..04daf82 100644 --- a/client/app/index.js +++ b/client/app/index.js @@ -31,6 +31,7 @@ import zipFiles from './components/Pages/Assignments/zipFiles'; import updateHandle from './components/Pages/Profile/UpdateHandle'; import contribute from './components/Pages/Contribute'; import ReactLoading from './components/common/Loading' +import CodeEditor from './components/Pages/CodeEditor' // import 'bootstrap/dist/css/bootstrap.min.css'; @@ -79,6 +80,8 @@ render(( + + diff --git a/config/webpack.common.js b/config/webpack.common.js index bbd7935..a33d2f8 100644 --- a/config/webpack.common.js +++ b/config/webpack.common.js @@ -3,6 +3,7 @@ const autoprefixer = require('autoprefixer'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); +const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const helpers = require('./helpers'); @@ -73,6 +74,10 @@ module.exports = { new CopyWebpackPlugin([{ from: helpers.root('client/public') - }]) + }]), + + new MonacoWebpackPlugin({ + languages: ['cpp','java','python'] + }) ] }; diff --git a/package.json b/package.json index fbf0101..81e305e 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "extract-text-webpack-plugin": "^4.0.0-beta.0", "html-webpack-plugin": "^3.1.0", "jsonwebtoken": "^8.3.0", + "monaco-editor-webpack-plugin": "^1.7.0", "mongoose": "^5.1.7", "multer": "^1.3.1", "node-sass": "^4.9.0", @@ -49,8 +50,10 @@ "react-avatar": "^3.4.6", "react-dom": "^16.2.0", "react-hot-loader": "^4.0.0", + "react-icons": "^3.7.0", "react-iframe": "^1.3.0", "react-loading": "^2.0.3", + "react-monaco-editor": "~0.26.2", "react-redux": "^5.0.7", "react-router": "^4.2.0", "react-router-dom": "^4.2.2",