From a5738d9b15f171bdf3fd28a2c9e1021e6d8586de Mon Sep 17 00:00:00 2001 From: nahbee10 Date: Tue, 6 Aug 2024 06:46:13 +0900 Subject: [PATCH 1/2] editor class component to fc conversion --- .../modules/IDE/components/Editor/index.jsx | 883 +++++++++--------- 1 file changed, 421 insertions(+), 462 deletions(-) diff --git a/client/modules/IDE/components/Editor/index.jsx b/client/modules/IDE/components/Editor/index.jsx index 8393116308..c13c34ea56 100644 --- a/client/modules/IDE/components/Editor/index.jsx +++ b/client/modules/IDE/components/Editor/index.jsx @@ -1,7 +1,5 @@ -// TODO: convert to functional component - import PropTypes from 'prop-types'; -import React from 'react'; +import React, { useEffect, useRef, useState, useCallback } from 'react'; import CodeMirror from 'codemirror'; import Fuse from 'fuse.js'; import emmet from '@emmetio/codemirror-plugin'; @@ -81,57 +79,249 @@ window.HTMLHint = HTMLHint; const INDENTATION_AMOUNT = 2; -class Editor extends React.Component { - constructor(props) { - super(props); - this.state = { - currentLine: 1 - }; - this._cm = null; - this.tidyCode = this.tidyCode.bind(this); +const getFileMode = (fileName) => { + let mode; + if (fileName.match(/.+\.js$/i)) { + mode = 'javascript'; + } else if (fileName.match(/.+\.css$/i)) { + mode = 'css'; + } else if (fileName.match(/.+\.(html|xml)$/i)) { + mode = 'htmlmixed'; + } else if (fileName.match(/.+\.json$/i)) { + mode = 'application/json'; + } else if (fileName.match(/.+\.(frag|glsl)$/i)) { + mode = 'x-shader/x-fragment'; + } else if (fileName.match(/.+\.(vert|stl|mtl)$/i)) { + mode = 'x-shader/x-vertex'; + } else { + mode = 'text/plain'; + } + return mode; +}; + +const prettierFormatWithCursor = (cmInstance, parser, plugins) => { + try { + const { formatted, cursorOffset } = prettier.formatWithCursor( + cmInstance.doc.getValue(), + { + cursorOffset: cmInstance.doc.indexFromPos(cmInstance.doc.getCursor()), + parser, + plugins + } + ); + const { left, top } = cmInstance.getScrollInfo(); + cmInstance.doc.setValue(formatted); + cmInstance.focus(); + cmInstance.doc.setCursor(cmInstance.doc.posFromIndex(cursorOffset)); + cmInstance.scrollTo(left, top); + } catch (error) { + console.error(error); + } +}; - this.updateLintingMessageAccessibility = debounce((annotations) => { - this.props.clearLintMessage(); +const Editor = (props) => { + const [currentLine, setCurrentLine] = useState(1); + const codemirrorContainer = useRef(null); + const cmInstance = useRef(null); + const beep = useRef(null); + const docs = useRef({}); + + const { + files, + file, + theme, + lineNumbers, + linewrap, + autocloseBracketsQuotes, + autocompleteHinter, + fontSize, + lintMessages, + lintWarning, + consoleEvents, + runtimeErrorWarningVisible, + unsavedChanges, + isPlaying, + autorefresh, + isExpanded, + collapseSidebar, + closeProjectOptions, + expandSidebar, + setUnsavedChanges, + hideRuntimeErrorWarning, + updateFileContent, + clearConsole, + startSketch, + expandConsole, + setSelectedFile, + t, + provideController, + clearLintMessage, + updateLintMessage + } = props; + + const tidyCode = useCallback(() => { + const mode = cmInstance.current.getOption('mode'); + if (mode === 'javascript') { + prettierFormatWithCursor(cmInstance.current, 'babel', [babelParser]); + } else if (mode === 'css') { + prettierFormatWithCursor(cmInstance.current, 'css', [cssParser]); + } else if (mode === 'htmlmixed') { + prettierFormatWithCursor(cmInstance.current, 'html', [htmlParser]); + } + }, []); + + const updateLintingMessageAccessibility = useCallback( + debounce((annotations) => { + clearLintMessage(); annotations.forEach((x) => { if (x.from.line > -1) { - this.props.updateLintMessage(x.severity, x.from.line + 1, x.message); + updateLintMessage(x.severity, x.from.line + 1, x.message); } }); - if (this.props.lintMessages.length > 0 && this.props.lintWarning) { - this.beep.play(); + if (lintMessages.length > 0 && lintWarning) { + beep.current.play(); } - }, 2000); - this.showFind = this.showFind.bind(this); - this.showReplace = this.showReplace.bind(this); - this.getContent = this.getContent.bind(this); - } + }, 2000), + [clearLintMessage, updateLintMessage, lintMessages.length, lintWarning] + ); - componentDidMount() { - this.beep = new Audio(beepUrl); - // this.widgets = []; - this._cm = CodeMirror(this.codemirrorContainer, { - theme: `p5-${this.props.theme}`, - lineNumbers: this.props.lineNumbers, + const showFind = useCallback(() => { + cmInstance.current.execCommand('findPersistent'); + }, []); + + const showReplace = useCallback(() => { + cmInstance.current.execCommand('replace'); + }, []); + + const getContent = useCallback(() => { + const content = cmInstance.current.getValue(); + return { ...file, content }; + }, [file]); + + const handleKeyUp = useCallback(() => { + const lineNumber = parseInt(cmInstance.current.getCursor().line + 1, 10); + setCurrentLine(lineNumber); + }, []); + + const showHint = useCallback( + (_cm) => { + if (!autocompleteHinter) { + CodeMirror.showHint(_cm, () => {}, {}); + return; + } + + let focusedLinkElement = null; + const setFocusedLinkElement = (set) => { + if (set && !focusedLinkElement) { + const activeItemLink = document.querySelector( + `.CodeMirror-hint-active a` + ); + if (activeItemLink) { + focusedLinkElement = activeItemLink; + focusedLinkElement.classList.add('focused-hint-link'); + focusedLinkElement.parentElement.parentElement.classList.add( + 'unfocused' + ); + } + } + }; + const removeFocusedLinkElement = () => { + if (focusedLinkElement) { + focusedLinkElement.classList.remove('focused-hint-link'); + focusedLinkElement.parentElement.parentElement.classList.remove( + 'unfocused' + ); + focusedLinkElement = null; + return true; + } + return false; + }; + + const hintOptions = { + _fontSize: fontSize, + completeSingle: false, + extraKeys: { + 'Shift-Right': (cm) => { + const activeItemLink = document.querySelector( + `.CodeMirror-hint-active a` + ); + if (activeItemLink) activeItemLink.click(); + }, + Right: (cm) => { + setFocusedLinkElement(true); + }, + Left: (cm) => { + removeFocusedLinkElement(); + }, + Up: (cm, e) => { + const onLink = removeFocusedLinkElement(); + e.moveFocus(-1); + setFocusedLinkElement(onLink); + }, + Down: (cm, e) => { + const onLink = removeFocusedLinkElement(); + e.moveFocus(1); + setFocusedLinkElement(onLink); + }, + Enter: (cm, e) => { + if (focusedLinkElement) focusedLinkElement.click(); + else e.pick(); + } + }, + closeOnUnfocus: false + }; + + if (_cm.options.mode === 'javascript') { + CodeMirror.showHint( + _cm, + () => { + const c = _cm.getCursor(); + const token = _cm.getTokenAt(c); + + const hints = hinter.p5Hinter + .search(token.string) + .filter((h) => h.item.text[0] === token.string[0]); + + return { + list: hints, + from: CodeMirror.Pos(c.line, token.start), + to: CodeMirror.Pos(c.line, c.ch) + }; + }, + hintOptions + ); + } else if (_cm.options.mode === 'css') { + CodeMirror.showHint(_cm, CodeMirror.hint.css, hintOptions); + } + }, + [autocompleteHinter, fontSize] + ); + + useEffect(() => { + beep.current = new Audio(beepUrl); + cmInstance.current = CodeMirror(codemirrorContainer.current, { + theme: `p5-${theme}`, + lineNumbers, styleActiveLine: true, inputStyle: 'contenteditable', - lineWrapping: this.props.linewrap, + lineWrapping: linewrap, fixedGutter: false, foldGutter: true, foldOptions: { widget: '\u2026' }, gutters: ['CodeMirror-foldgutter', 'CodeMirror-lint-markers'], keyMap: 'sublime', - highlightSelectionMatches: true, // highlight current search match + highlightSelectionMatches: true, matchBrackets: true, emmet: { preview: ['html'], markTagPairs: true, autoRenameTags: true }, - autoCloseBrackets: this.props.autocloseBracketsQuotes, + autoCloseBrackets: autocloseBracketsQuotes, styleSelectedText: true, lint: { onUpdateLinting: (annotations) => { - this.updateLintingMessageAccessibility(annotations); + updateLintingMessageAccessibility(annotations); }, options: { asi: true, @@ -146,24 +336,19 @@ class Editor extends React.Component { } }); - this.hinter = new Fuse(hinter.p5Hinter, { - threshold: 0.05, - keys: ['text'] - }); - - delete this._cm.options.lint.options.errors; + const cm = cmInstance.current; + emmet(CodeMirror); const replaceCommand = metaKey === 'Ctrl' ? `${metaKey}-H` : `${metaKey}-Option-F`; - this._cm.setOption('extraKeys', { - Tab: (cm) => { - if (!cm.execCommand('emmetExpandAbbreviation')) return; - // might need to specify and indent more? - const selection = cm.doc.getSelection(); + cm.setOption('extraKeys', { + Tab: (_cmInstance) => { + if (!_cmInstance.execCommand('emmetExpandAbbreviation')) return; + const selection = _cmInstance.doc.getSelection(); if (selection.length > 0) { - cm.execCommand('indentMore'); + _cmInstance.execCommand('indentMore'); } else { - cm.replaceSelection(' '.repeat(INDENTATION_AMOUNT)); + _cmInstance.replaceSelection(' '.repeat(INDENTATION_AMOUNT)); } }, Enter: 'emmetInsertLineBreak', @@ -171,131 +356,103 @@ class Editor extends React.Component { [`${metaKey}-Enter`]: () => null, [`Shift-${metaKey}-Enter`]: () => null, [`${metaKey}-F`]: 'findPersistent', - [`Shift-${metaKey}-F`]: this.tidyCode, + [`Shift-${metaKey}-F`]: tidyCode, [`${metaKey}-G`]: 'findPersistentNext', [`Shift-${metaKey}-G`]: 'findPersistentPrev', [replaceCommand]: 'replace', - // Cassie Tarakajian: If you don't set a default color, then when you - // choose a color, it deletes characters inline. This is a - // hack to prevent that. - [`${metaKey}-K`]: (cm, event) => - cm.state.colorpicker.popup_color_picker({ length: 0 }), - [`${metaKey}-.`]: 'toggleComment' // Note: most adblockers use the shortcut ctrl+. + [`${metaKey}-K`]: (_cmInstMetaKey, event) => + _cmInstMetaKey.state.colorpicker.popup_color_picker({ length: 0 }), + [`${metaKey}-.`]: 'toggleComment' }); - this.initializeDocuments(this.props.files); - this._cm.swapDoc(this._docs[this.props.file.id]); - - this._cm.on( + cm.on( 'change', debounce(() => { - this.props.setUnsavedChanges(true); - this.props.hideRuntimeErrorWarning(); - this.props.updateFileContent(this.props.file.id, this._cm.getValue()); - if (this.props.autorefresh && this.props.isPlaying) { - this.props.clearConsole(); - this.props.startSketch(); + setUnsavedChanges(true); + hideRuntimeErrorWarning(); + updateFileContent(file.id, cm.getValue()); + if (autorefresh && isPlaying) { + clearConsole(); + startSketch(); } }, 1000) ); - if (this._cm) { - this._cm.on('keyup', this.handleKeyUp); + if (cm) { + cm.on('keyup', handleKeyUp); } - this._cm.on('keydown', (_cm, e) => { - // Show hint - const mode = this._cm.getOption('mode'); + cm.on('keydown', (_cm, e) => { + const mode = cm.getOption('mode'); if (/^[a-z]$/i.test(e.key) && (mode === 'css' || mode === 'javascript')) { - this.showHint(_cm); + showHint(cm); } if (e.key === 'Escape') { e.preventDefault(); - this._cm.getInputField().blur(); + cm.getInputField().blur(); } }); - this._cm.getWrapperElement().style[ - 'font-size' - ] = `${this.props.fontSize}px`; + cm.getWrapperElement().style['font-size'] = `${fontSize}px`; - this.props.provideController({ - tidyCode: this.tidyCode, - showFind: this.showFind, - showReplace: this.showReplace, - getContent: this.getContent + provideController({ + tidyCode, + showFind, + showReplace, + getContent }); - } - - componentWillUpdate(nextProps) { - // check if files have changed - if (this.props.files[0].id !== nextProps.files[0].id) { - // then need to make CodeMirror documents - this.initializeDocuments(nextProps.files); - } - if (this.props.files.length !== nextProps.files.length) { - this.initializeDocuments(nextProps.files); - } - } - componentDidUpdate(prevProps) { - if (this.props.file.id !== prevProps.file.id) { - const fileMode = this.getFileMode(this.props.file.name); - if (fileMode === 'javascript') { - // Define the new Emmet configuration based on the file mode - const emmetConfig = { - preview: ['html'], - markTagPairs: false, - autoRenameTags: true - }; - this._cm.setOption('emmet', emmetConfig); + return () => { + if (cm) { + cm.off('keyup', handleKeyUp); } - const oldDoc = this._cm.swapDoc(this._docs[this.props.file.id]); - this._docs[prevProps.file.id] = oldDoc; - this._cm.focus(); + provideController(null); + }; + }, [ + theme, + lineNumbers, + linewrap, + autocloseBracketsQuotes, + fontSize, + tidyCode, + updateLintingMessageAccessibility, + setUnsavedChanges, + hideRuntimeErrorWarning, + updateFileContent, + autorefresh, + isPlaying, + clearConsole, + startSketch, + provideController, + showFind, + showReplace, + getContent, + handleKeyUp, + showHint + ]); + + useEffect(() => { + const initializeDocuments = (fileList) => { + docs.current = {}; + fileList.forEach((f) => { + if (f.name !== 'root') { + docs.current[f.id] = CodeMirror.Doc(f.content, getFileMode(f.name)); + } + }); + }; - if (!prevProps.unsavedChanges) { - setTimeout(() => this.props.setUnsavedChanges(false), 400); - } - } - if (this.props.fontSize !== prevProps.fontSize) { - this._cm.getWrapperElement().style[ - 'font-size' - ] = `${this.props.fontSize}px`; - } - if (this.props.linewrap !== prevProps.linewrap) { - this._cm.setOption('lineWrapping', this.props.linewrap); - } - if (this.props.theme !== prevProps.theme) { - this._cm.setOption('theme', `p5-${this.props.theme}`); - } - if (this.props.lineNumbers !== prevProps.lineNumbers) { - this._cm.setOption('lineNumbers', this.props.lineNumbers); - } - if ( - this.props.autocloseBracketsQuotes !== prevProps.autocloseBracketsQuotes - ) { - this._cm.setOption( - 'autoCloseBrackets', - this.props.autocloseBracketsQuotes - ); - } - if (this.props.autocompleteHinter !== prevProps.autocompleteHinter) { - if (!this.props.autocompleteHinter) { - // close the hinter window once the preference is turned off - CodeMirror.showHint(this._cm, () => {}, {}); - } - } + initializeDocuments(files); + cmInstance.current.swapDoc(docs.current[file.id]); + }, [files, file.id]); - if (this.props.runtimeErrorWarningVisible) { - if (this.props.consoleEvents.length !== prevProps.consoleEvents.length) { - this.props.consoleEvents.forEach((consoleEvent) => { + useEffect(() => { + if (runtimeErrorWarningVisible) { + if (consoleEvents.length > 0) { + consoleEvents.forEach((consoleEvent) => { if (consoleEvent.method === 'error') { - // It doesn't work if you create a new Error, but this works - // LOL const errorObj = { stack: consoleEvent.data[0].toString() }; StackTrace.fromError(errorObj).then((stackLines) => { - this.props.expandConsole(); + expandConsole(); const line = stackLines.find( (l) => l.fileName && l.fileName.startsWith('/') ); @@ -303,11 +460,11 @@ class Editor extends React.Component { const fileNameArray = line.fileName.split('/'); const fileName = fileNameArray.slice(-1)[0]; const filePath = fileNameArray.slice(0, -1).join('/'); - const fileWithError = this.props.files.find( + const fileWithError = files.find( (f) => f.name === fileName && f.filePath === filePath ); - this.props.setSelectedFile(fileWithError.id); - this._cm.addLineClass( + setSelectedFile(fileWithError.id); + cmInstance.current.addLineClass( line.lineNumber - 1, 'background', 'line-runtime-error' @@ -316,306 +473,113 @@ class Editor extends React.Component { } }); } else { - for (let i = 0; i < this._cm.lineCount(); i += 1) { - this._cm.removeLineClass(i, 'background', 'line-runtime-error'); - } - } - } - - if (this.props.file.id !== prevProps.file.id) { - for (let i = 0; i < this._cm.lineCount(); i += 1) { - this._cm.removeLineClass(i, 'background', 'line-runtime-error'); - } - } - - this.props.provideController({ - tidyCode: this.tidyCode, - showFind: this.showFind, - showReplace: this.showReplace, - getContent: this.getContent - }); - } - - componentWillUnmount() { - if (this._cm) { - this._cm.off('keyup', this.handleKeyUp); - } - this.props.provideController(null); - } - - getFileMode(fileName) { - let mode; - if (fileName.match(/.+\.js$/i)) { - mode = 'javascript'; - } else if (fileName.match(/.+\.css$/i)) { - mode = 'css'; - } else if (fileName.match(/.+\.(html|xml)$/i)) { - mode = 'htmlmixed'; - } else if (fileName.match(/.+\.json$/i)) { - mode = 'application/json'; - } else if (fileName.match(/.+\.(frag|glsl)$/i)) { - mode = 'x-shader/x-fragment'; - } else if (fileName.match(/.+\.(vert|stl|mtl)$/i)) { - mode = 'x-shader/x-vertex'; - } else { - mode = 'text/plain'; - } - return mode; - } - - getContent() { - const content = this._cm.getValue(); - const updatedFile = Object.assign({}, this.props.file, { content }); - return updatedFile; - } - - handleKeyUp = () => { - const lineNumber = parseInt(this._cm.getCursor().line + 1, 10); - this.setState({ currentLine: lineNumber }); - }; - - showFind() { - this._cm.execCommand('findPersistent'); - } - - showHint(_cm) { - if (!this.props.autocompleteHinter) { - CodeMirror.showHint(_cm, () => {}, {}); - return; - } - - let focusedLinkElement = null; - const setFocusedLinkElement = (set) => { - if (set && !focusedLinkElement) { - const activeItemLink = document.querySelector( - `.CodeMirror-hint-active a` - ); - if (activeItemLink) { - focusedLinkElement = activeItemLink; - focusedLinkElement.classList.add('focused-hint-link'); - focusedLinkElement.parentElement.parentElement.classList.add( - 'unfocused' + for (let i = 0; i < cmInstance.current.lineCount(); i += 1) { + cmInstance.current.removeLineClass( + i, + 'background', + 'line-runtime-error' ); } } - }; - const removeFocusedLinkElement = () => { - if (focusedLinkElement) { - focusedLinkElement.classList.remove('focused-hint-link'); - focusedLinkElement.parentElement.parentElement.classList.remove( - 'unfocused' - ); - focusedLinkElement = null; - return true; - } - return false; - }; - - const hintOptions = { - _fontSize: this.props.fontSize, - completeSingle: false, - extraKeys: { - 'Shift-Right': (cm, e) => { - const activeItemLink = document.querySelector( - `.CodeMirror-hint-active a` - ); - if (activeItemLink) activeItemLink.click(); - }, - Right: (cm, e) => { - setFocusedLinkElement(true); - }, - Left: (cm, e) => { - removeFocusedLinkElement(); - }, - Up: (cm, e) => { - const onLink = removeFocusedLinkElement(); - e.moveFocus(-1); - setFocusedLinkElement(onLink); - }, - Down: (cm, e) => { - const onLink = removeFocusedLinkElement(); - e.moveFocus(1); - setFocusedLinkElement(onLink); - }, - Enter: (cm, e) => { - if (focusedLinkElement) focusedLinkElement.click(); - else e.pick(); - } - }, - closeOnUnfocus: false - }; - - if (_cm.options.mode === 'javascript') { - // JavaScript - CodeMirror.showHint( - _cm, - () => { - const c = _cm.getCursor(); - const token = _cm.getTokenAt(c); - - const hints = this.hinter - .search(token.string) - .filter((h) => h.item.text[0] === token.string[0]); - - return { - list: hints, - from: CodeMirror.Pos(c.line, token.start), - to: CodeMirror.Pos(c.line, c.ch) - }; - }, - hintOptions - ); - } else if (_cm.options.mode === 'css') { - // CSS - CodeMirror.showHint(_cm, CodeMirror.hint.css, hintOptions); } - } - showReplace() { - this._cm.execCommand('replace'); - } - - prettierFormatWithCursor(parser, plugins) { - try { - const { formatted, cursorOffset } = prettier.formatWithCursor( - this._cm.doc.getValue(), - { - cursorOffset: this._cm.doc.indexFromPos(this._cm.doc.getCursor()), - parser, - plugins - } - ); - const { left, top } = this._cm.getScrollInfo(); - this._cm.doc.setValue(formatted); - this._cm.focus(); - this._cm.doc.setCursor(this._cm.doc.posFromIndex(cursorOffset)); - this._cm.scrollTo(left, top); - } catch (error) { - console.error(error); - } - } - - tidyCode() { - const mode = this._cm.getOption('mode'); - if (mode === 'javascript') { - this.prettierFormatWithCursor('babel', [babelParser]); - } else if (mode === 'css') { - this.prettierFormatWithCursor('css', [cssParser]); - } else if (mode === 'htmlmixed') { - this.prettierFormatWithCursor('html', [htmlParser]); + for (let i = 0; i < cmInstance.current.lineCount(); i += 1) { + cmInstance.current.removeLineClass(i, 'background', 'line-runtime-error'); } - } - - initializeDocuments(files) { - this._docs = {}; - files.forEach((file) => { - if (file.name !== 'root') { - this._docs[file.id] = CodeMirror.Doc( - file.content, - this.getFileMode(file.name) - ); // eslint-disable-line - } - }); - } - - render() { - const editorSectionClass = classNames({ - editor: true, - 'sidebar--contracted': !this.props.isExpanded - }); - const editorHolderClass = classNames({ - 'editor-holder': true, - 'editor-holder--hidden': - this.props.file.fileType === 'folder' || this.props.file.url + provideController({ + tidyCode, + showFind, + showReplace, + getContent }); - - const { currentLine } = this.state; - - return ( - - {(matches) => - matches ? ( -
-
- - -
- - {this.props.file.name} - - - -
-
-
{ - this.codemirrorContainer = element; + }, [ + runtimeErrorWarningVisible, + consoleEvents, + expandConsole, + files, + file.id, + setSelectedFile, + provideController, + tidyCode, + showFind, + showReplace, + getContent + ]); + + const editorSectionClass = classNames({ + editor: true, + 'sidebar--contracted': !isExpanded + }); + + const editorHolderClass = classNames({ + 'editor-holder': true, + 'editor-holder--hidden': file.fileType === 'folder' || file.url + }); + + return ( + + {(matches) => + matches ? ( +
+
+ + +
+ + {file.name} + + + +
+
+
+ {file.url ? : null} + +
+ ) : ( + +
+ + + {file.name} + + +
+
+ + {file.url ? ( + ) : null}
- ) : ( - -
- - - {this.props.file.name} - - -
-
- { - this.codemirrorContainer = element; - }} - /> - {this.props.file.url ? ( - - ) : null} - -
-
- ) - } -
- ); - } -} + + ) + } + + ); +}; Editor.propTypes = { autocloseBracketsQuotes: PropTypes.bool.isRequired, @@ -675,41 +639,36 @@ Editor.propTypes = { expandConsole: PropTypes.func.isRequired }; -function mapStateToProps(state) { - return { - files: state.files, - file: selectActiveFile(state), - htmlFile: getHTMLFile(state.files), - ide: state.ide, - preferences: state.preferences, - editorAccessibility: state.editorAccessibility, - user: state.user, - project: state.project, - consoleEvents: state.console, - - ...state.preferences, - ...state.ide, - ...state.project, - ...state.editorAccessibility, - isExpanded: state.ide.sidebarIsExpanded - }; -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators( - Object.assign( - {}, - EditorAccessibilityActions, - FileActions, - ProjectActions, - IDEActions, - PreferencesActions, - UserActions, - ConsoleActions - ), +const mapStateToProps = (state) => ({ + files: state.files, + file: selectActiveFile(state), + htmlFile: getHTMLFile(state.files), + ide: state.ide, + preferences: state.preferences, + editorAccessibility: state.editorAccessibility, + user: state.user, + project: state.project, + consoleEvents: state.console, + ...state.preferences, + ...state.ide, + ...state.project, + ...state.editorAccessibility, + isExpanded: state.ide.sidebarIsExpanded +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + ...EditorAccessibilityActions, + ...FileActions, + ...ProjectActions, + ...IDEActions, + ...PreferencesActions, + ...UserActions, + ...ConsoleActions + }, dispatch ); -} export default withTranslation()( connect(mapStateToProps, mapDispatchToProps)(Editor) From df4ef470b401ba51252711e9c9c5abbee96607fe Mon Sep 17 00:00:00 2001 From: nahbee10 Date: Tue, 6 Aug 2024 06:46:13 +0900 Subject: [PATCH 2/2] editor class component to fc conversion --- .../modules/IDE/components/Editor/index.jsx | 883 +++++++++--------- 1 file changed, 421 insertions(+), 462 deletions(-) diff --git a/client/modules/IDE/components/Editor/index.jsx b/client/modules/IDE/components/Editor/index.jsx index 8393116308..c13c34ea56 100644 --- a/client/modules/IDE/components/Editor/index.jsx +++ b/client/modules/IDE/components/Editor/index.jsx @@ -1,7 +1,5 @@ -// TODO: convert to functional component - import PropTypes from 'prop-types'; -import React from 'react'; +import React, { useEffect, useRef, useState, useCallback } from 'react'; import CodeMirror from 'codemirror'; import Fuse from 'fuse.js'; import emmet from '@emmetio/codemirror-plugin'; @@ -81,57 +79,249 @@ window.HTMLHint = HTMLHint; const INDENTATION_AMOUNT = 2; -class Editor extends React.Component { - constructor(props) { - super(props); - this.state = { - currentLine: 1 - }; - this._cm = null; - this.tidyCode = this.tidyCode.bind(this); +const getFileMode = (fileName) => { + let mode; + if (fileName.match(/.+\.js$/i)) { + mode = 'javascript'; + } else if (fileName.match(/.+\.css$/i)) { + mode = 'css'; + } else if (fileName.match(/.+\.(html|xml)$/i)) { + mode = 'htmlmixed'; + } else if (fileName.match(/.+\.json$/i)) { + mode = 'application/json'; + } else if (fileName.match(/.+\.(frag|glsl)$/i)) { + mode = 'x-shader/x-fragment'; + } else if (fileName.match(/.+\.(vert|stl|mtl)$/i)) { + mode = 'x-shader/x-vertex'; + } else { + mode = 'text/plain'; + } + return mode; +}; + +const prettierFormatWithCursor = (cmInstance, parser, plugins) => { + try { + const { formatted, cursorOffset } = prettier.formatWithCursor( + cmInstance.doc.getValue(), + { + cursorOffset: cmInstance.doc.indexFromPos(cmInstance.doc.getCursor()), + parser, + plugins + } + ); + const { left, top } = cmInstance.getScrollInfo(); + cmInstance.doc.setValue(formatted); + cmInstance.focus(); + cmInstance.doc.setCursor(cmInstance.doc.posFromIndex(cursorOffset)); + cmInstance.scrollTo(left, top); + } catch (error) { + console.error(error); + } +}; - this.updateLintingMessageAccessibility = debounce((annotations) => { - this.props.clearLintMessage(); +const Editor = (props) => { + const [currentLine, setCurrentLine] = useState(1); + const codemirrorContainer = useRef(null); + const cmInstance = useRef(null); + const beep = useRef(null); + const docs = useRef({}); + + const { + files, + file, + theme, + lineNumbers, + linewrap, + autocloseBracketsQuotes, + autocompleteHinter, + fontSize, + lintMessages, + lintWarning, + consoleEvents, + runtimeErrorWarningVisible, + unsavedChanges, + isPlaying, + autorefresh, + isExpanded, + collapseSidebar, + closeProjectOptions, + expandSidebar, + setUnsavedChanges, + hideRuntimeErrorWarning, + updateFileContent, + clearConsole, + startSketch, + expandConsole, + setSelectedFile, + t, + provideController, + clearLintMessage, + updateLintMessage + } = props; + + const tidyCode = useCallback(() => { + const mode = cmInstance.current.getOption('mode'); + if (mode === 'javascript') { + prettierFormatWithCursor(cmInstance.current, 'babel', [babelParser]); + } else if (mode === 'css') { + prettierFormatWithCursor(cmInstance.current, 'css', [cssParser]); + } else if (mode === 'htmlmixed') { + prettierFormatWithCursor(cmInstance.current, 'html', [htmlParser]); + } + }, []); + + const updateLintingMessageAccessibility = useCallback( + debounce((annotations) => { + clearLintMessage(); annotations.forEach((x) => { if (x.from.line > -1) { - this.props.updateLintMessage(x.severity, x.from.line + 1, x.message); + updateLintMessage(x.severity, x.from.line + 1, x.message); } }); - if (this.props.lintMessages.length > 0 && this.props.lintWarning) { - this.beep.play(); + if (lintMessages.length > 0 && lintWarning) { + beep.current.play(); } - }, 2000); - this.showFind = this.showFind.bind(this); - this.showReplace = this.showReplace.bind(this); - this.getContent = this.getContent.bind(this); - } + }, 2000), + [clearLintMessage, updateLintMessage, lintMessages.length, lintWarning] + ); - componentDidMount() { - this.beep = new Audio(beepUrl); - // this.widgets = []; - this._cm = CodeMirror(this.codemirrorContainer, { - theme: `p5-${this.props.theme}`, - lineNumbers: this.props.lineNumbers, + const showFind = useCallback(() => { + cmInstance.current.execCommand('findPersistent'); + }, []); + + const showReplace = useCallback(() => { + cmInstance.current.execCommand('replace'); + }, []); + + const getContent = useCallback(() => { + const content = cmInstance.current.getValue(); + return { ...file, content }; + }, [file]); + + const handleKeyUp = useCallback(() => { + const lineNumber = parseInt(cmInstance.current.getCursor().line + 1, 10); + setCurrentLine(lineNumber); + }, []); + + const showHint = useCallback( + (_cm) => { + if (!autocompleteHinter) { + CodeMirror.showHint(_cm, () => {}, {}); + return; + } + + let focusedLinkElement = null; + const setFocusedLinkElement = (set) => { + if (set && !focusedLinkElement) { + const activeItemLink = document.querySelector( + `.CodeMirror-hint-active a` + ); + if (activeItemLink) { + focusedLinkElement = activeItemLink; + focusedLinkElement.classList.add('focused-hint-link'); + focusedLinkElement.parentElement.parentElement.classList.add( + 'unfocused' + ); + } + } + }; + const removeFocusedLinkElement = () => { + if (focusedLinkElement) { + focusedLinkElement.classList.remove('focused-hint-link'); + focusedLinkElement.parentElement.parentElement.classList.remove( + 'unfocused' + ); + focusedLinkElement = null; + return true; + } + return false; + }; + + const hintOptions = { + _fontSize: fontSize, + completeSingle: false, + extraKeys: { + 'Shift-Right': (cm) => { + const activeItemLink = document.querySelector( + `.CodeMirror-hint-active a` + ); + if (activeItemLink) activeItemLink.click(); + }, + Right: (cm) => { + setFocusedLinkElement(true); + }, + Left: (cm) => { + removeFocusedLinkElement(); + }, + Up: (cm, e) => { + const onLink = removeFocusedLinkElement(); + e.moveFocus(-1); + setFocusedLinkElement(onLink); + }, + Down: (cm, e) => { + const onLink = removeFocusedLinkElement(); + e.moveFocus(1); + setFocusedLinkElement(onLink); + }, + Enter: (cm, e) => { + if (focusedLinkElement) focusedLinkElement.click(); + else e.pick(); + } + }, + closeOnUnfocus: false + }; + + if (_cm.options.mode === 'javascript') { + CodeMirror.showHint( + _cm, + () => { + const c = _cm.getCursor(); + const token = _cm.getTokenAt(c); + + const hints = hinter.p5Hinter + .search(token.string) + .filter((h) => h.item.text[0] === token.string[0]); + + return { + list: hints, + from: CodeMirror.Pos(c.line, token.start), + to: CodeMirror.Pos(c.line, c.ch) + }; + }, + hintOptions + ); + } else if (_cm.options.mode === 'css') { + CodeMirror.showHint(_cm, CodeMirror.hint.css, hintOptions); + } + }, + [autocompleteHinter, fontSize] + ); + + useEffect(() => { + beep.current = new Audio(beepUrl); + cmInstance.current = CodeMirror(codemirrorContainer.current, { + theme: `p5-${theme}`, + lineNumbers, styleActiveLine: true, inputStyle: 'contenteditable', - lineWrapping: this.props.linewrap, + lineWrapping: linewrap, fixedGutter: false, foldGutter: true, foldOptions: { widget: '\u2026' }, gutters: ['CodeMirror-foldgutter', 'CodeMirror-lint-markers'], keyMap: 'sublime', - highlightSelectionMatches: true, // highlight current search match + highlightSelectionMatches: true, matchBrackets: true, emmet: { preview: ['html'], markTagPairs: true, autoRenameTags: true }, - autoCloseBrackets: this.props.autocloseBracketsQuotes, + autoCloseBrackets: autocloseBracketsQuotes, styleSelectedText: true, lint: { onUpdateLinting: (annotations) => { - this.updateLintingMessageAccessibility(annotations); + updateLintingMessageAccessibility(annotations); }, options: { asi: true, @@ -146,24 +336,19 @@ class Editor extends React.Component { } }); - this.hinter = new Fuse(hinter.p5Hinter, { - threshold: 0.05, - keys: ['text'] - }); - - delete this._cm.options.lint.options.errors; + const cm = cmInstance.current; + emmet(CodeMirror); const replaceCommand = metaKey === 'Ctrl' ? `${metaKey}-H` : `${metaKey}-Option-F`; - this._cm.setOption('extraKeys', { - Tab: (cm) => { - if (!cm.execCommand('emmetExpandAbbreviation')) return; - // might need to specify and indent more? - const selection = cm.doc.getSelection(); + cm.setOption('extraKeys', { + Tab: (_cmInstance) => { + if (!_cmInstance.execCommand('emmetExpandAbbreviation')) return; + const selection = _cmInstance.doc.getSelection(); if (selection.length > 0) { - cm.execCommand('indentMore'); + _cmInstance.execCommand('indentMore'); } else { - cm.replaceSelection(' '.repeat(INDENTATION_AMOUNT)); + _cmInstance.replaceSelection(' '.repeat(INDENTATION_AMOUNT)); } }, Enter: 'emmetInsertLineBreak', @@ -171,131 +356,103 @@ class Editor extends React.Component { [`${metaKey}-Enter`]: () => null, [`Shift-${metaKey}-Enter`]: () => null, [`${metaKey}-F`]: 'findPersistent', - [`Shift-${metaKey}-F`]: this.tidyCode, + [`Shift-${metaKey}-F`]: tidyCode, [`${metaKey}-G`]: 'findPersistentNext', [`Shift-${metaKey}-G`]: 'findPersistentPrev', [replaceCommand]: 'replace', - // Cassie Tarakajian: If you don't set a default color, then when you - // choose a color, it deletes characters inline. This is a - // hack to prevent that. - [`${metaKey}-K`]: (cm, event) => - cm.state.colorpicker.popup_color_picker({ length: 0 }), - [`${metaKey}-.`]: 'toggleComment' // Note: most adblockers use the shortcut ctrl+. + [`${metaKey}-K`]: (_cmInstMetaKey, event) => + _cmInstMetaKey.state.colorpicker.popup_color_picker({ length: 0 }), + [`${metaKey}-.`]: 'toggleComment' }); - this.initializeDocuments(this.props.files); - this._cm.swapDoc(this._docs[this.props.file.id]); - - this._cm.on( + cm.on( 'change', debounce(() => { - this.props.setUnsavedChanges(true); - this.props.hideRuntimeErrorWarning(); - this.props.updateFileContent(this.props.file.id, this._cm.getValue()); - if (this.props.autorefresh && this.props.isPlaying) { - this.props.clearConsole(); - this.props.startSketch(); + setUnsavedChanges(true); + hideRuntimeErrorWarning(); + updateFileContent(file.id, cm.getValue()); + if (autorefresh && isPlaying) { + clearConsole(); + startSketch(); } }, 1000) ); - if (this._cm) { - this._cm.on('keyup', this.handleKeyUp); + if (cm) { + cm.on('keyup', handleKeyUp); } - this._cm.on('keydown', (_cm, e) => { - // Show hint - const mode = this._cm.getOption('mode'); + cm.on('keydown', (_cm, e) => { + const mode = cm.getOption('mode'); if (/^[a-z]$/i.test(e.key) && (mode === 'css' || mode === 'javascript')) { - this.showHint(_cm); + showHint(cm); } if (e.key === 'Escape') { e.preventDefault(); - this._cm.getInputField().blur(); + cm.getInputField().blur(); } }); - this._cm.getWrapperElement().style[ - 'font-size' - ] = `${this.props.fontSize}px`; + cm.getWrapperElement().style['font-size'] = `${fontSize}px`; - this.props.provideController({ - tidyCode: this.tidyCode, - showFind: this.showFind, - showReplace: this.showReplace, - getContent: this.getContent + provideController({ + tidyCode, + showFind, + showReplace, + getContent }); - } - - componentWillUpdate(nextProps) { - // check if files have changed - if (this.props.files[0].id !== nextProps.files[0].id) { - // then need to make CodeMirror documents - this.initializeDocuments(nextProps.files); - } - if (this.props.files.length !== nextProps.files.length) { - this.initializeDocuments(nextProps.files); - } - } - componentDidUpdate(prevProps) { - if (this.props.file.id !== prevProps.file.id) { - const fileMode = this.getFileMode(this.props.file.name); - if (fileMode === 'javascript') { - // Define the new Emmet configuration based on the file mode - const emmetConfig = { - preview: ['html'], - markTagPairs: false, - autoRenameTags: true - }; - this._cm.setOption('emmet', emmetConfig); + return () => { + if (cm) { + cm.off('keyup', handleKeyUp); } - const oldDoc = this._cm.swapDoc(this._docs[this.props.file.id]); - this._docs[prevProps.file.id] = oldDoc; - this._cm.focus(); + provideController(null); + }; + }, [ + theme, + lineNumbers, + linewrap, + autocloseBracketsQuotes, + fontSize, + tidyCode, + updateLintingMessageAccessibility, + setUnsavedChanges, + hideRuntimeErrorWarning, + updateFileContent, + autorefresh, + isPlaying, + clearConsole, + startSketch, + provideController, + showFind, + showReplace, + getContent, + handleKeyUp, + showHint + ]); + + useEffect(() => { + const initializeDocuments = (fileList) => { + docs.current = {}; + fileList.forEach((f) => { + if (f.name !== 'root') { + docs.current[f.id] = CodeMirror.Doc(f.content, getFileMode(f.name)); + } + }); + }; - if (!prevProps.unsavedChanges) { - setTimeout(() => this.props.setUnsavedChanges(false), 400); - } - } - if (this.props.fontSize !== prevProps.fontSize) { - this._cm.getWrapperElement().style[ - 'font-size' - ] = `${this.props.fontSize}px`; - } - if (this.props.linewrap !== prevProps.linewrap) { - this._cm.setOption('lineWrapping', this.props.linewrap); - } - if (this.props.theme !== prevProps.theme) { - this._cm.setOption('theme', `p5-${this.props.theme}`); - } - if (this.props.lineNumbers !== prevProps.lineNumbers) { - this._cm.setOption('lineNumbers', this.props.lineNumbers); - } - if ( - this.props.autocloseBracketsQuotes !== prevProps.autocloseBracketsQuotes - ) { - this._cm.setOption( - 'autoCloseBrackets', - this.props.autocloseBracketsQuotes - ); - } - if (this.props.autocompleteHinter !== prevProps.autocompleteHinter) { - if (!this.props.autocompleteHinter) { - // close the hinter window once the preference is turned off - CodeMirror.showHint(this._cm, () => {}, {}); - } - } + initializeDocuments(files); + cmInstance.current.swapDoc(docs.current[file.id]); + }, [files, file.id]); - if (this.props.runtimeErrorWarningVisible) { - if (this.props.consoleEvents.length !== prevProps.consoleEvents.length) { - this.props.consoleEvents.forEach((consoleEvent) => { + useEffect(() => { + if (runtimeErrorWarningVisible) { + if (consoleEvents.length > 0) { + consoleEvents.forEach((consoleEvent) => { if (consoleEvent.method === 'error') { - // It doesn't work if you create a new Error, but this works - // LOL const errorObj = { stack: consoleEvent.data[0].toString() }; StackTrace.fromError(errorObj).then((stackLines) => { - this.props.expandConsole(); + expandConsole(); const line = stackLines.find( (l) => l.fileName && l.fileName.startsWith('/') ); @@ -303,11 +460,11 @@ class Editor extends React.Component { const fileNameArray = line.fileName.split('/'); const fileName = fileNameArray.slice(-1)[0]; const filePath = fileNameArray.slice(0, -1).join('/'); - const fileWithError = this.props.files.find( + const fileWithError = files.find( (f) => f.name === fileName && f.filePath === filePath ); - this.props.setSelectedFile(fileWithError.id); - this._cm.addLineClass( + setSelectedFile(fileWithError.id); + cmInstance.current.addLineClass( line.lineNumber - 1, 'background', 'line-runtime-error' @@ -316,306 +473,113 @@ class Editor extends React.Component { } }); } else { - for (let i = 0; i < this._cm.lineCount(); i += 1) { - this._cm.removeLineClass(i, 'background', 'line-runtime-error'); - } - } - } - - if (this.props.file.id !== prevProps.file.id) { - for (let i = 0; i < this._cm.lineCount(); i += 1) { - this._cm.removeLineClass(i, 'background', 'line-runtime-error'); - } - } - - this.props.provideController({ - tidyCode: this.tidyCode, - showFind: this.showFind, - showReplace: this.showReplace, - getContent: this.getContent - }); - } - - componentWillUnmount() { - if (this._cm) { - this._cm.off('keyup', this.handleKeyUp); - } - this.props.provideController(null); - } - - getFileMode(fileName) { - let mode; - if (fileName.match(/.+\.js$/i)) { - mode = 'javascript'; - } else if (fileName.match(/.+\.css$/i)) { - mode = 'css'; - } else if (fileName.match(/.+\.(html|xml)$/i)) { - mode = 'htmlmixed'; - } else if (fileName.match(/.+\.json$/i)) { - mode = 'application/json'; - } else if (fileName.match(/.+\.(frag|glsl)$/i)) { - mode = 'x-shader/x-fragment'; - } else if (fileName.match(/.+\.(vert|stl|mtl)$/i)) { - mode = 'x-shader/x-vertex'; - } else { - mode = 'text/plain'; - } - return mode; - } - - getContent() { - const content = this._cm.getValue(); - const updatedFile = Object.assign({}, this.props.file, { content }); - return updatedFile; - } - - handleKeyUp = () => { - const lineNumber = parseInt(this._cm.getCursor().line + 1, 10); - this.setState({ currentLine: lineNumber }); - }; - - showFind() { - this._cm.execCommand('findPersistent'); - } - - showHint(_cm) { - if (!this.props.autocompleteHinter) { - CodeMirror.showHint(_cm, () => {}, {}); - return; - } - - let focusedLinkElement = null; - const setFocusedLinkElement = (set) => { - if (set && !focusedLinkElement) { - const activeItemLink = document.querySelector( - `.CodeMirror-hint-active a` - ); - if (activeItemLink) { - focusedLinkElement = activeItemLink; - focusedLinkElement.classList.add('focused-hint-link'); - focusedLinkElement.parentElement.parentElement.classList.add( - 'unfocused' + for (let i = 0; i < cmInstance.current.lineCount(); i += 1) { + cmInstance.current.removeLineClass( + i, + 'background', + 'line-runtime-error' ); } } - }; - const removeFocusedLinkElement = () => { - if (focusedLinkElement) { - focusedLinkElement.classList.remove('focused-hint-link'); - focusedLinkElement.parentElement.parentElement.classList.remove( - 'unfocused' - ); - focusedLinkElement = null; - return true; - } - return false; - }; - - const hintOptions = { - _fontSize: this.props.fontSize, - completeSingle: false, - extraKeys: { - 'Shift-Right': (cm, e) => { - const activeItemLink = document.querySelector( - `.CodeMirror-hint-active a` - ); - if (activeItemLink) activeItemLink.click(); - }, - Right: (cm, e) => { - setFocusedLinkElement(true); - }, - Left: (cm, e) => { - removeFocusedLinkElement(); - }, - Up: (cm, e) => { - const onLink = removeFocusedLinkElement(); - e.moveFocus(-1); - setFocusedLinkElement(onLink); - }, - Down: (cm, e) => { - const onLink = removeFocusedLinkElement(); - e.moveFocus(1); - setFocusedLinkElement(onLink); - }, - Enter: (cm, e) => { - if (focusedLinkElement) focusedLinkElement.click(); - else e.pick(); - } - }, - closeOnUnfocus: false - }; - - if (_cm.options.mode === 'javascript') { - // JavaScript - CodeMirror.showHint( - _cm, - () => { - const c = _cm.getCursor(); - const token = _cm.getTokenAt(c); - - const hints = this.hinter - .search(token.string) - .filter((h) => h.item.text[0] === token.string[0]); - - return { - list: hints, - from: CodeMirror.Pos(c.line, token.start), - to: CodeMirror.Pos(c.line, c.ch) - }; - }, - hintOptions - ); - } else if (_cm.options.mode === 'css') { - // CSS - CodeMirror.showHint(_cm, CodeMirror.hint.css, hintOptions); } - } - showReplace() { - this._cm.execCommand('replace'); - } - - prettierFormatWithCursor(parser, plugins) { - try { - const { formatted, cursorOffset } = prettier.formatWithCursor( - this._cm.doc.getValue(), - { - cursorOffset: this._cm.doc.indexFromPos(this._cm.doc.getCursor()), - parser, - plugins - } - ); - const { left, top } = this._cm.getScrollInfo(); - this._cm.doc.setValue(formatted); - this._cm.focus(); - this._cm.doc.setCursor(this._cm.doc.posFromIndex(cursorOffset)); - this._cm.scrollTo(left, top); - } catch (error) { - console.error(error); - } - } - - tidyCode() { - const mode = this._cm.getOption('mode'); - if (mode === 'javascript') { - this.prettierFormatWithCursor('babel', [babelParser]); - } else if (mode === 'css') { - this.prettierFormatWithCursor('css', [cssParser]); - } else if (mode === 'htmlmixed') { - this.prettierFormatWithCursor('html', [htmlParser]); + for (let i = 0; i < cmInstance.current.lineCount(); i += 1) { + cmInstance.current.removeLineClass(i, 'background', 'line-runtime-error'); } - } - - initializeDocuments(files) { - this._docs = {}; - files.forEach((file) => { - if (file.name !== 'root') { - this._docs[file.id] = CodeMirror.Doc( - file.content, - this.getFileMode(file.name) - ); // eslint-disable-line - } - }); - } - - render() { - const editorSectionClass = classNames({ - editor: true, - 'sidebar--contracted': !this.props.isExpanded - }); - const editorHolderClass = classNames({ - 'editor-holder': true, - 'editor-holder--hidden': - this.props.file.fileType === 'folder' || this.props.file.url + provideController({ + tidyCode, + showFind, + showReplace, + getContent }); - - const { currentLine } = this.state; - - return ( - - {(matches) => - matches ? ( -
-
- - -
- - {this.props.file.name} - - - -
-
-
{ - this.codemirrorContainer = element; + }, [ + runtimeErrorWarningVisible, + consoleEvents, + expandConsole, + files, + file.id, + setSelectedFile, + provideController, + tidyCode, + showFind, + showReplace, + getContent + ]); + + const editorSectionClass = classNames({ + editor: true, + 'sidebar--contracted': !isExpanded + }); + + const editorHolderClass = classNames({ + 'editor-holder': true, + 'editor-holder--hidden': file.fileType === 'folder' || file.url + }); + + return ( + + {(matches) => + matches ? ( +
+
+ + +
+ + {file.name} + + + +
+
+
+ {file.url ? : null} + +
+ ) : ( + +
+ + + {file.name} + + +
+
+ + {file.url ? ( + ) : null}
- ) : ( - -
- - - {this.props.file.name} - - -
-
- { - this.codemirrorContainer = element; - }} - /> - {this.props.file.url ? ( - - ) : null} - -
-
- ) - } -
- ); - } -} + + ) + } + + ); +}; Editor.propTypes = { autocloseBracketsQuotes: PropTypes.bool.isRequired, @@ -675,41 +639,36 @@ Editor.propTypes = { expandConsole: PropTypes.func.isRequired }; -function mapStateToProps(state) { - return { - files: state.files, - file: selectActiveFile(state), - htmlFile: getHTMLFile(state.files), - ide: state.ide, - preferences: state.preferences, - editorAccessibility: state.editorAccessibility, - user: state.user, - project: state.project, - consoleEvents: state.console, - - ...state.preferences, - ...state.ide, - ...state.project, - ...state.editorAccessibility, - isExpanded: state.ide.sidebarIsExpanded - }; -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators( - Object.assign( - {}, - EditorAccessibilityActions, - FileActions, - ProjectActions, - IDEActions, - PreferencesActions, - UserActions, - ConsoleActions - ), +const mapStateToProps = (state) => ({ + files: state.files, + file: selectActiveFile(state), + htmlFile: getHTMLFile(state.files), + ide: state.ide, + preferences: state.preferences, + editorAccessibility: state.editorAccessibility, + user: state.user, + project: state.project, + consoleEvents: state.console, + ...state.preferences, + ...state.ide, + ...state.project, + ...state.editorAccessibility, + isExpanded: state.ide.sidebarIsExpanded +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + ...EditorAccessibilityActions, + ...FileActions, + ...ProjectActions, + ...IDEActions, + ...PreferencesActions, + ...UserActions, + ...ConsoleActions + }, dispatch ); -} export default withTranslation()( connect(mapStateToProps, mapDispatchToProps)(Editor)