Created
March 11, 2024 16:54
-
-
Save Pratyush-Dehury/74bba9a6fd03e6560cffe3460a154645 to your computer and use it in GitHub Desktop.
Sample changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState, useEffect, useRef } from 'react'; | |
import CodeMirror from 'codemirror'; | |
import debounce from 'lodash.debounce'; | |
import Fuse from 'fuse.js'; | |
import { StackTrace } from 'stacktrace-js'; | |
import classNames from 'classnames'; | |
import MediaQuery from 'react-responsive'; | |
import IconButton from './IconButton'; | |
import Timer from './Timer'; | |
import UnsavedChangesIndicator from './UnsavedChangesIndicator'; | |
import EditorAccessibility from './EditorAccessibility'; | |
import AssetPreview from './AssetPreview'; | |
import { LeftArrowIcon, RightArrowIcon, FolderIcon } from './Icons'; | |
const Editor = ({ | |
files, | |
file, | |
lintMessages, | |
clearLintMessage, | |
updateLintMessage, | |
lintWarning, | |
collapseSidebar, | |
expandSidebar, | |
expandConsole, | |
hideRuntimeErrorWarning, | |
setUnsavedChanges, | |
unsavedChanges, | |
updateFileContent, | |
provideController, | |
setSelectedFile, | |
startSketch, | |
clearConsole, | |
consoleEvents, | |
isPlaying, | |
closeProjectOptions, | |
fontSize, | |
lineNumbers, | |
linewrap, | |
theme, | |
autocloseBracketsQuotes, | |
autocompleteHinter, | |
runtimeErrorWarningVisible, | |
isExpanded, | |
}) => { | |
const codemirrorContainer = useRef(null); | |
const [cm, setCm] = useState(null); | |
const hinter = useRef(null); | |
useEffect(() => { | |
const beep = new Audio(beepUrl); | |
const initializeEditor = () => { | |
const _cm = CodeMirror(codemirrorContainer.current, { | |
theme: `p5-${theme}`, | |
lineNumbers, | |
styleActiveLine: true, | |
inputStyle: 'contenteditable', | |
lineWrapping: linewrap, | |
fixedGutter: false, | |
foldGutter: true, | |
foldOptions: { widget: '\u2026' }, | |
gutters: ['CodeMirror-foldgutter', 'CodeMirror-lint-markers'], | |
keyMap: 'sublime', | |
highlightSelectionMatches: true, | |
matchBrackets: true, | |
emmet: { | |
preview: ['html'], | |
markTagPairs: true, | |
autoRenameTags: true, | |
}, | |
autoCloseBrackets: autocloseBracketsQuotes, | |
styleSelectedText: true, | |
lint: { | |
onUpdateLinting: (annotations) => { | |
updateLintingMessageAccessibility(annotations); | |
}, | |
options: { | |
asi: true, | |
eqeqeq: false, | |
'-W041': false, | |
esversion: 7, | |
}, | |
}, | |
colorpicker: { | |
type: 'sketch', | |
mode: 'edit', | |
}, | |
}); | |
setCm(_cm); | |
hinter.current = new Fuse(hinter.p5Hinter, { | |
threshold: 0.05, | |
keys: ['text'], | |
}); | |
delete _cm.options.lint.options.errors; | |
const replaceCommand = metaKey === 'Ctrl' ? `${metaKey}-H` : `${metaKey}-Option-F`; | |
_cm.setOption('extraKeys', { | |
Tab: (cm) => { | |
if (!cm.execCommand('emmetExpandAbbreviation')) return; | |
const selection = cm.doc.getSelection(); | |
if (selection.length > 0) { | |
cm.execCommand('indentMore'); | |
} else { | |
cm.replaceSelection(' '.repeat(INDENTATION_AMOUNT)); | |
} | |
}, | |
Enter: 'emmetInsertLineBreak', | |
Esc: 'emmetResetAbbreviation', | |
[`${metaKey}-Enter`]: () => null, | |
[`Shift-${metaKey}-Enter`]: () => null, | |
[`${metaKey}-F`]: 'findPersistent', | |
[`Shift-${metaKey}-F`]: tidyCode, | |
[`${metaKey}-G`]: 'findPersistentNext', | |
[`Shift-${metaKey}-G`]: 'findPersistentPrev', | |
[replaceCommand]: 'replace', | |
[`${metaKey}-K`]: (cm, event) => cm.state.colorpicker.popup_color_picker({ length: 0 }), | |
[`${metaKey}-.`]: 'toggleComment', | |
}); | |
_cm.on('change', debounce(() => { | |
setUnsavedChanges(true); | |
hideRuntimeErrorWarning(); | |
updateFileContent(file.id, _cm.getValue()); | |
if (autorefresh && isPlaying) { | |
clearConsole(); | |
startSketch(); | |
} | |
}, 1000)); | |
_cm.on('keyup', () => { | |
const temp = t('Editor.KeyUpLineNumber', { | |
lineNumber: parseInt(_cm.getCursor().line + 1, 10), | |
}); | |
document.getElementById('current-line').innerHTML = temp; | |
}); | |
_cm.on('keydown', (_cm, e) => { | |
const mode = _cm.getOption('mode'); | |
if (/^[a-z]$/i.test(e.key) && (mode === 'css' || mode === 'javascript')) { | |
showHint(_cm); | |
} | |
}); | |
_cm.getWrapperElement().style['font-size'] = `${fontSize}px`; | |
provideController({ | |
tidyCode, | |
showFind, | |
showReplace, | |
getContent, | |
}); | |
}; | |
initializeEditor(); | |
return () => { | |
if (cm) { | |
cm.toTextArea(); | |
} | |
provideController(null); | |
}; | |
}, []); | |
useEffect(() => { | |
if (cm) { | |
cm.setOption('mode', getFileMode(file.name)); | |
const oldDoc = cm.swapDoc(docs[file.id]); | |
docs[prevProps.file.id] = oldDoc; | |
cm.focus(); | |
if (!unsavedChanges) { | |
setTimeout(() => setUnsavedChanges(false), 400); | |
} | |
} | |
}, [file]); | |
useEffect(() => { | |
if (cm) { | |
cm.getWrapperElement().style['font-size'] = `${fontSize}px`; | |
} | |
}, [fontSize]); | |
useEffect(() => { | |
if (cm) { | |
cm.setOption('lineWrapping', linewrap); | |
} | |
}, [linewrap]); | |
useEffect(() => { | |
if (cm) { | |
cm.setOption('theme', `p5-${theme}`); | |
} | |
}, [theme]); | |
useEffect(() => { | |
if (cm) { | |
cm.setOption('lineNumbers', lineNumbers); | |
} | |
}, [lineNumbers]); | |
useEffect(() => { | |
if (cm) { | |
cm.setOption('autoCloseBrackets', autocloseBracketsQuotes); | |
} | |
}, [autocloseBracketsQuotes]); | |
useEffect(() => { | |
if (cm) { | |
if (!autocompleteHinter) { | |
CodeMirror.showHint(cm, () => {}, {}); | |
} | |
} | |
}, [autocompleteHinter]); | |
useEffect(() => { | |
if (cm && runtimeErrorWarningVisible) { | |
if (consoleEvents.length !== 0) { | |
consoleEvents.forEach((consoleEvent) => { | |
if (consoleEvent.method === 'error') { | |
const errorObj = { stack: consoleEvent.data[0].toString() }; | |
StackTrace.fromError(errorObj).then((stackLines) => { | |
expandConsole(); | |
const line = stackLines.find((l) => l.fileName && l.fileName.startsWith('/')); | |
if (!line) return; | |
const fileNameArray = line.fileName.split('/'); | |
const fileName = fileNameArray.slice(-1)[0]; | |
const filePath = fileNameArray.slice(0, -1).join('/'); | |
const fileWithError = files.find((f) => f.name === fileName && f.filePath === filePath); | |
setSelectedFile(fileWithError.id); | |
cm.addLineClass(line.lineNumber - 1, 'background', 'line-runtime-error'); | |
}); | |
} | |
}); | |
} else { | |
for (let i = 0; i < cm.lineCount(); i += 1) { | |
cm.removeLineClass(i, 'background', 'line-runtime-error'); | |
} | |
} | |
} | |
return () => { | |
if (cm) { | |
for (let i = 0; i < cm.lineCount(); i += 1) { | |
cm.removeLineClass(i, 'background', 'line-runtime-error'); | |
} | |
} | |
}; | |
}, [runtimeErrorWarningVisible, consoleEvents]); | |
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)$/i)) { | |
mode = 'x-shader/x-vertex'; | |
} else { | |
mode = 'text/plain'; | |
} | |
return mode; | |
}; | |
const getContent = () => { | |
const content = cm.getValue(); | |
const updatedFile = Object.assign({}, file, { content }); | |
return updatedFile; | |
}; | |
const showFind = () => { | |
cm.execCommand('findPersistent'); | |
}; | |
const showHint = (_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, 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') { | |
const c = _cm.getCursor(); | |
const token = _cm.getTokenAt(c); | |
const hints = hinter.current | |
.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), | |
}; | |
} else if (_cm.options.mode === 'css') { | |
return CodeMirror.hint.css; | |
} | |
}; | |
const showReplace = () => { | |
cm.execCommand('replace'); | |
}; | |
const prettierFormatWithCursor = (parser, plugins) => { | |
try { | |
const { formatted, cursorOffset } = prettier.formatWithCursor( | |
cm.doc.getValue(), | |
{ | |
cursorOffset: cm.doc.indexFromPos(cm.doc.getCursor()), | |
parser, | |
plugins, | |
} | |
); | |
const { left, top } = cm.getScrollInfo(); | |
cm.doc.setValue(formatted); | |
cm.focus(); | |
cm.doc.setCursor(cm.doc.posFromIndex(cursorOffset)); | |
cm.scrollTo(left, top); | |
} catch (error) { | |
console.error(error); | |
} | |
}; | |
const tidyCode = () => { | |
const mode = cm.getOption('mode'); | |
if (mode === 'javascript') { | |
prettierFormatWithCursor('babel', [babelParser]); | |
} else if (mode === 'css') { | |
prettierFormatWithCursor('css', [cssParser]); | |
} else if (mode === 'htmlmixed') { | |
prettierFormatWithCursor('html', [htmlParser]); | |
} | |
}; | |
const editorSectionClass = classNames({ | |
editor: true, | |
'sidebar--contracted': !isExpanded, | |
}); | |
const editorHolderClass = classNames({ | |
'editor-holder': true, | |
'editor-holder--hidden': | |
file.fileType === 'folder' || file.url, | |
}); | |
return ( | |
<MediaQuery minWidth={770}> | |
{(matches) => | |
matches ? ( | |
<section className={editorSectionClass}> | |
<header className="editor__header"> | |
<button | |
aria-label={t('Editor.OpenSketchARIA')} | |
className="sidebar__contract" | |
onClick={() => { | |
collapseSidebar(); | |
closeProjectOptions(); | |
}} | |
> | |
<LeftArrowIcon focusable="false" aria-hidden="true" /> | |
</button> | |
<button | |
aria-label={t('Editor.CloseSketchARIA')} | |
className="sidebar__expand" | |
onClick={expandSidebar} | |
> | |
<RightArrowIcon focusable="false" aria-hidden="true" /> | |
</button> | |
<div className="editor__file-name"> | |
<span> | |
{file.name} | |
<UnsavedChangesIndicator /> | |
</span> | |
<Timer /> | |
</div> | |
</header> | |
<article | |
ref={codemirrorContainer} | |
className={editorHolderClass} | |
/> | |
{file.url ? ( | |
<AssetPreview | |
url={file.url} | |
name={file.name} | |
/> | |
) : null} | |
<EditorAccessibility lintMessages={lintMessages} /> | |
</section> | |
) : ( | |
<EditorContainer expanded={isExpanded}> | |
<header> | |
<IconButton | |
onClick={expandSidebar} | |
icon={FolderIcon} | |
/> | |
<span> | |
{file.name} | |
<UnsavedChangesIndicator /> | |
</span> | |
</header> | |
<section> | |
<EditorHolder | |
ref={codemirrorContainer} | |
/> | |
{file.url ? ( | |
<AssetPreview | |
url={file.url} | |
name={file.name} | |
/> | |
) : null} | |
<EditorAccessibility lintMessages={lintMessages} /> | |
</section> | |
</EditorContainer> | |
) | |
} | |
</MediaQuery> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment