seperate codeeditor code

This commit is contained in:
SArpnt 2024-03-13 16:20:53 -04:00
parent 39840b5b3c
commit 33bbaae8a8
Signed by: SArpnt
SSH Key Fingerprint: SHA256:iDMeic8KkqqEsN4wODlgsk1d/oW1ojZ/cu/MEWyfLBw
10 changed files with 118 additions and 114 deletions

View File

@ -1,7 +1,7 @@
import { f32ToRound10, isPlainObject, Song, SongMode } from "./common.ts";
import { elements as elementsPromise } from "./dom.ts";
import elementsPromise from "./elements.ts";
import { fromUrlData, setUrlData } from "./url/mod.ts";
import type { EditorView } from "@codemirror/view";
import { initCodeEditor, getCode, setCode } from "./code-editor/mod.ts";
const searchParams = new URLSearchParams(location.search);
@ -41,19 +41,16 @@ let timeUnit = null;
let animationFrameId = null;
let codeEditor: HTMLTextAreaElement | EditorView | null = null;
let elements = null; // TODO remove
let elements: Awaited<typeof elementsPromise> | null = null; // TODO remove
(async function init() {
await initAudioContext();
elements = await elementsPromise;
initCodeEditor(refreshCode);
ctx = elements.canvas.getContext("2d", { alpha: false });
initTextarea(document.getElementById("code-editor"));
import("./codemirror.ts").then(o => initCodemirror(o.default));
if (globalThis.loadLibrary !== false) {
import("./library/load.ts");
}
@ -155,104 +152,10 @@ function handleMessage(e) {
}
}
let saveData = null;
function initTextarea(textarea: HTMLTextAreaElement) {
textarea.addEventListener("input", () => refreshCode());
{
let keyTrap = true;
textarea.addEventListener("keydown", e => {
if (!e.altKey && !e.ctrlKey) {
if (e.key === "Escape") {
if (keyTrap) {
e.preventDefault();
keyTrap = false;
}
} else if (e.key === "Tab" && keyTrap) {
e.preventDefault();
const { selectionStart, selectionEnd } = textarea;
if (e.shiftKey) {
// remove indentation on all selected lines
let lines = textarea.value.split("\n");
let getLine = char => {
let line = 0;
for (let c = 0; ; line++) {
c += lines[line].length;
if (c > char) 1;
break;
}
return line;
};
let startLine = getLine(selectionStart);
let endLine = getLine(selectionEnd);
let newSelectionStart = selectionStart;
let newSelectionEnd = selectionEnd;
for (let i = startLine; i <= endLine; i++) {
if (lines[i][0] === "\t") {
lines[i] = lines[i].slice(1);
if (i === startLine) {
newSelectionStart--;
}
newSelectionEnd--;
}
}
textarea.value = lines.join("\n");
textarea.setSelectionRange(newSelectionStart, newSelectionEnd);
} else {
// add tab character
textarea.value = `${el.value.slice(0, selectionStart)}\t${el.value.slice(
selectionEnd,
)}`;
textarea.setSelectionRange(selectionStart + 1, selectionStart + 1);
}
refreshCode();
} else {
keyTrap = false;
}
}
});
}
codeEditor = textarea;
}
function initCodemirror(createCodemirrorEditor) {
const codemirror = createCodemirrorEditor(() => refreshCode());
let selection;
if (codeEditor) {
codemirror.dispatch({ changes: { from: 0, insert: codeEditor.value } });
if (document.activeElement === codeEditor) {
selection = {
anchor: codeEditor.selectionStart,
head: codeEditor.selectionEnd,
};
}
}
(codeEditor ?? document.getElementById("code-editor")).replaceWith(codemirror.dom);
if (selection) {
codemirror.focus();
codemirror.dispatch({ selection });
}
codeEditor = codemirror;
}
function getCodeEditorText(): string {
if (codeEditor instanceof Element) {
return codeEditor.value;
} else {
return codeEditor.state.doc.toString();
}
}
function setCodeEditorText(value?: string) {
if (codeEditor instanceof Element) {
codeEditor.value = value as string;
} else {
codeEditor.dispatch({
changes: { from: 0, to: codeEditor.state.doc.length, insert: value },
});
}
}
function refreshCode() {
if (audioWorklet) {
audioWorklet.port.postMessage({ code: getCodeEditorText().trim() });
audioWorklet.port.postMessage({ code: getCode().trim() });
}
}
function handleWindowResize(force: boolean) {
@ -297,14 +200,14 @@ function animationFrame() {
}
function getSong(): Song {
return { code: getCodeEditorText(), ...songData };
return { code: getCode(), ...songData };
}
function setSong(songData?: Partial<Song>, play = true) {
let code, sampleRate, mode;
if (songData) {
({ code, sampleRate, mode } = songData);
setCodeEditorText(code);
setCode(code);
}
setSampleRate(sampleRate ?? 8000);
applyPlaybackMode(mode ?? "Bytebeat");

View File

@ -6,8 +6,8 @@ import { indentUnit, bracketMatching, syntaxHighlighting } from "@codemirror/lan
import { javascript } from "@codemirror/lang-javascript";
import { classHighlighter } from "@lezer/highlight";
export default function createCodemirror(inputListener?: (() => void)) {
const codeEditor = new EditorView({
function createCodemirror(inputListener?: (() => void)) {
const editor = new EditorView({
state: EditorState.create({
extensions: [
keymap.of([
@ -37,8 +37,29 @@ export default function createCodemirror(inputListener?: (() => void)) {
}),
});
codeEditor.dom.id = "code-editor";
codeEditor.dom.ariaLabel = "Code editor";
editor.dom.id = "code-editor";
editor.dom.ariaLabel = "Code editor";
return codeEditor;
return editor;
}
// replace a textarea with codemirror
function replace(editCallback: () => void, textarea: HTMLTextAreaElement): EditorView {
const codemirror = createCodemirror(editCallback);
codemirror.dispatch({ changes: { from: 0, insert: textarea.value } });
let selection;
if (document.activeElement === textarea) {
selection = {
anchor: textarea.selectionStart,
head: textarea.selectionEnd,
};
}
textarea.replaceWith(codemirror.dom);
if (selection) {
codemirror.focus();
codemirror.dispatch({ selection });
}
return codemirror;
}
export { EditorView, replace };

24
src/code-editor/mod.ts Normal file
View File

@ -0,0 +1,24 @@
import initTextarea from "./textarea.ts";
import type { EditorView } from "./codemirror.ts";
let getCode: () => string;
let setCode: (value?: string) => void;
// must be ran after dom is loaded
function initCodeEditor(editCallback: () => void) {
const textarea: HTMLTextAreaElement = document.getElementById("code-editor");
initTextarea(editCallback, textarea);
getCode = () => textarea.value;
setCode = v => {textarea.value = v as string};
// codemirror takes a long time to load, so import it late, then replace the textarea
import("./codemirror.ts").then(c => {
const codemirror = c.replace(editCallback, textarea);
getCode = () => codemirror.state.doc.toString();
setCode = v => codemirror.dispatch({
changes: { from: 0, to: codemirror.state.doc.length, insert: v },
});
});
}
export { initCodeEditor, getCode, setCode };

View File

@ -0,0 +1,58 @@
function initTextarea(editCallback: () => void, textarea: HTMLTextAreaElement) {
textarea.addEventListener("input", () => editCallback());
let keyTrap = true;
textarea.addEventListener("keydown", e => {
if (!e.altKey && !e.ctrlKey) {
if (e.key === "Escape") {
if (keyTrap) {
e.preventDefault();
keyTrap = false;
}
} else if (e.key === "Tab" && keyTrap) {
e.preventDefault();
const { selectionStart, selectionEnd } = textarea;
if (e.shiftKey) {
// remove indentation on all selected lines
let lines = textarea.value.split("\n");
let getLine = (char: number) => {
let line = 0;
for (let c = 0; ; line++) {
c += lines[line].length;
if (c > char) 1;
break;
}
return line;
};
let startLine = getLine(selectionStart);
let endLine = getLine(selectionEnd);
let newSelectionStart = selectionStart;
let newSelectionEnd = selectionEnd;
for (let i = startLine; i <= endLine; i++) {
if (lines[i][0] === "\t") {
lines[i] = lines[i].slice(1);
if (i === startLine) {
newSelectionStart--;
}
newSelectionEnd--;
}
}
textarea.value = lines.join("\n");
textarea.setSelectionRange(newSelectionStart, newSelectionEnd);
} else {
// add tab character
textarea.value = `${textarea.value.slice(0, selectionStart)}\t${textarea.value.slice(
selectionEnd,
)}`;
textarea.setSelectionRange(selectionStart + 1, selectionStart + 1);
}
editCallback();
} else {
keyTrap = false;
}
}
});
}
export default initTextarea;

View File

@ -6,7 +6,7 @@ const domLoaded = new Promise(resolve => {
}
});
const elements = (async () => {
export default (async () => {
await domLoaded;
return {
main: document.getElementsByTagName("main")[0],
@ -31,5 +31,3 @@ const elements = (async () => {
canvasContainer: document.getElementById("canvas-container")!,
};
})();
export { elements };

View File

@ -1,5 +1,5 @@
import { isPlainObject } from "./common.ts";
import { elements } from "./dom.ts";
import elements from "./elements.ts";
import { setCanvasWidth, autoSizeCanvas, getSong, setSong } from "./bytebeat.ts";
elements.then(elements => {