Skip to content

Instantly share code, notes, and snippets.

@Pratyush-Dehury
Created March 11, 2024 16:54
Show Gist options
  • Save Pratyush-Dehury/74bba9a6fd03e6560cffe3460a154645 to your computer and use it in GitHub Desktop.
Save Pratyush-Dehury/74bba9a6fd03e6560cffe3460a154645 to your computer and use it in GitHub Desktop.
Sample changes
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