seperate codeeditor code
This commit is contained in:
parent
39840b5b3c
commit
33bbaae8a8
113
src/bytebeat.ts
113
src/bytebeat.ts
|
@ -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");
|
||||
|
|
|
@ -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 };
|
|
@ -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 };
|
|
@ -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;
|
|
@ -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 };
|
|
@ -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 => {
|
||||
|
|
Loading…
Reference in New Issue