Felix Roos ea0e0b4396 reset canvas when pattern changes
+ rename noteroll -> punchcard
2023-01-13 12:57:35 +01:00

435 lines
13 KiB
JavaScript

import l, { useCallback as w, useRef as A, useEffect as k, useMemo as $, useState as _, useLayoutEffect as Y } from "react";
import ie from "@uiw/react-codemirror";
import { Decoration as M, EditorView as Z } from "@codemirror/view";
import { StateEffect as ee, StateField as te } from "@codemirror/state";
import { javascript as le } from "@codemirror/lang-javascript";
import { tags as i } from "@lezer/highlight";
import { createTheme as ue } from "@uiw/codemirror-themes";
import { webaudioOutput as de, getAudioContext as fe } from "@strudel.cycles/webaudio";
import { useInView as me } from "react-hook-inview";
import { repl as he, logger as ge } from "@strudel.cycles/core";
import { transpiler as pe } from "@strudel.cycles/transpiler";
const ve = ue({
theme: "dark",
settings: {
background: "#222",
foreground: "#75baff",
caret: "#ffcc00",
selection: "rgba(128, 203, 196, 0.5)",
selectionMatch: "#036dd626",
lineHighlight: "#00000050",
gutterBackground: "transparent",
gutterForeground: "#8a919966"
},
styles: [
{ tag: i.keyword, color: "#c792ea" },
{ tag: i.operator, color: "#89ddff" },
{ tag: i.special(i.variableName), color: "#eeffff" },
{ tag: i.typeName, color: "#c3e88d" },
{ tag: i.atom, color: "#f78c6c" },
{ tag: i.number, color: "#c3e88d" },
{ tag: i.definition(i.variableName), color: "#82aaff" },
{ tag: i.string, color: "#c3e88d" },
{ tag: i.special(i.string), color: "#c3e88d" },
{ tag: i.comment, color: "#7d8799" },
{ tag: i.variableName, color: "#c792ea" },
{ tag: i.tagName, color: "#c3e88d" },
{ tag: i.bracket, color: "#525154" },
{ tag: i.meta, color: "#ffcb6b" },
{ tag: i.attributeName, color: "#c792ea" },
{ tag: i.propertyName, color: "#c792ea" },
{ tag: i.className, color: "#decb6b" },
{ tag: i.invalid, color: "#ffffff" }
]
});
const G = ee.define(), be = te.define({
create() {
return M.none;
},
update(e, r) {
try {
for (let t of r.effects)
if (t.is(G))
if (t.value) {
const o = M.mark({ attributes: { style: "background-color: #FFCA2880" } });
e = M.set([o.range(0, r.newDoc.length)]);
} else
e = M.set([]);
return e;
} catch (t) {
return console.warn("flash error", t), e;
}
},
provide: (e) => Z.decorations.from(e)
}), Ee = (e) => {
e.dispatch({ effects: G.of(!0) }), setTimeout(() => {
e.dispatch({ effects: G.of(!1) });
}, 200);
}, O = ee.define(), ye = te.define({
create() {
return M.none;
},
update(e, r) {
try {
for (let t of r.effects)
if (t.is(O)) {
const o = t.value.map(
(u) => (u.context.locations || []).map(({ start: m, end: d }) => {
const a = u.context.color || "#FFCA28";
let c = r.newDoc.line(m.line).from + m.column, h = r.newDoc.line(d.line).from + d.column;
const g = r.newDoc.length;
return c > g || h > g ? void 0 : M.mark({ attributes: { style: `outline: 1.5px solid ${a};` } }).range(c, h);
})
).flat().filter(Boolean) || [];
e = M.set(o, !0);
}
return e;
} catch {
return M.set([]);
}
},
provide: (e) => Z.decorations.from(e)
}), we = [le(), ve, ye, be];
function ke({ value: e, onChange: r, onViewChanged: t, onSelectionChange: o, options: u, editorDidMount: m }) {
const d = w(
(h) => {
r?.(h);
},
[r]
), a = w(
(h) => {
t?.(h);
},
[t]
), c = w(
(h) => {
h.selectionSet && o && o?.(h.state.selection);
},
[o]
);
return /* @__PURE__ */ l.createElement(l.Fragment, null, /* @__PURE__ */ l.createElement(ie, {
value: e,
onChange: d,
onCreateEditor: a,
onUpdate: c,
extensions: we
}));
}
function T(...e) {
return e.filter(Boolean).join(" ");
}
function Fe({ view: e, pattern: r, active: t, getTime: o }) {
const u = A([]), m = A();
k(() => {
if (e)
if (r && t) {
let d = requestAnimationFrame(function a() {
try {
const c = o(), g = [Math.max(m.current || c, c - 1 / 10, 0), c + 1 / 60];
m.current = g[1], u.current = u.current.filter((p) => p.whole.end > c);
const n = r.queryArc(...g).filter((p) => p.hasOnset());
u.current = u.current.concat(n), e.dispatch({ effects: O.of(u.current) });
} catch {
e.dispatch({ effects: O.of([]) });
}
d = requestAnimationFrame(a);
});
return () => {
cancelAnimationFrame(d);
};
} else
u.current = [], e.dispatch({ effects: O.of([]) });
}, [r, t, e]);
}
function _e(e, r = !1) {
const t = A(), o = A(), u = (a) => {
if (o.current !== void 0) {
const c = a - o.current;
e(a, c);
}
o.current = a, t.current = requestAnimationFrame(u);
}, m = () => {
t.current = requestAnimationFrame(u);
}, d = () => {
t.current && cancelAnimationFrame(t.current), delete t.current;
};
return k(() => {
t.current && (d(), m());
}, [e]), k(() => (r && m(), d), []), {
start: m,
stop: d
};
}
function Me({ pattern: e, started: r, getTime: t, onDraw: o, drawTime: u = [-2, 2] }) {
let [m, d] = u;
m = Math.abs(m);
let a = A([]), c = A(null);
k(() => {
if (e) {
const n = t(), p = e.queryArc(n, n + d);
a.current = a.current.filter((b) => b.whole.begin < n), a.current = a.current.concat(p);
}
}, [e]);
const { start: h, stop: g } = _e(
w(() => {
const n = t() + d;
if (c.current === null) {
c.current = n;
return;
}
const p = e.queryArc(Math.max(c.current, n - 1 / 10), n);
c.current = n, a.current = (a.current || []).filter((b) => b.whole.end >= n - m - d).concat(p.filter((b) => b.hasOnset())), o(e, n - d, a.current, u);
}, [e])
);
return k(() => {
r ? h() : (a.current = [], g());
}, [r]), {
clear: () => {
a.current = [];
}
};
}
function Ae(e) {
return k(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), w((r) => window.postMessage(r, "*"), []);
}
function Ne({
defaultOutput: e,
interval: r,
getTime: t,
evalOnMount: o = !1,
initialCode: u = "",
autolink: m = !1,
beforeEval: d,
afterEval: a,
onEvalError: c,
onToggle: h,
canvasId: g,
drawContext: n,
drawTime: p = [-2, 2]
}) {
const b = $(() => De(), []);
g = g || `canvas-${b}`;
const [q, x] = _(), [C, z] = _(), [E, D] = _(u), [H, B] = _(), [N, P] = _(), [R, S] = _(!1), I = E !== H, { scheduler: s, evaluate: v, start: J, stop: V, pause: re } = $(
() => he({
interval: r,
defaultOutput: e,
onSchedulerError: x,
onEvalError: (f) => {
z(f), c?.(f);
},
getTime: t,
drawContext: n,
transpiler: pe,
beforeEval: ({ code: f }) => {
D(f), d?.();
},
afterEval: ({ pattern: f, code: y }) => {
B(y), P(f), z(), x(), m && (window.location.hash = "#" + encodeURIComponent(btoa(y))), a?.();
},
onToggle: (f) => {
S(f), h?.(f);
}
}),
[e, r, t]
), ne = Ae(({ data: { from: f, type: y } }) => {
y === "start" && f !== b && V();
}), K = w(
async (f = !0) => {
const y = await v(E, f);
return ne({ type: "start", from: b }), y;
},
[v, E]
), L = w(
(f, y, U, W) => {
const { onPaint: ce } = f.context || {}, se = typeof n == "function" ? n(g) : n;
ce?.(se, y, U, W);
},
[n, g]
), j = w(
(f) => {
if (n && L) {
const [y, U] = p, W = f.queryArc(0, U);
L(f, 0, W, p);
}
},
[p, L]
), Q = A();
k(() => {
!Q.current && n && L && o && E && (Q.current = !0, v(E, !1).then((f) => j(f)));
}, [K, o, E, j]), k(() => () => {
s.stop();
}, [s]);
const oe = async () => {
R ? (s.stop(), j(N)) : await K();
}, ae = q || C;
return Me({
pattern: N,
started: n && R,
getTime: () => s.now(),
drawTime: p,
onDraw: L
}), {
id: b,
canvasId: g,
code: E,
setCode: D,
error: ae,
schedulerError: q,
scheduler: s,
evalError: C,
evaluate: v,
activateCode: K,
activeCode: H,
isDirty: I,
pattern: N,
started: R,
start: J,
stop: V,
pause: re,
togglePlay: oe
};
}
function De() {
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
}
function X({ type: e }) {
return /* @__PURE__ */ l.createElement("svg", {
xmlns: "http://www.w3.org/2000/svg",
className: "sc-h-5 sc-w-5",
viewBox: "0 0 20 20",
fill: "currentColor"
}, {
refresh: /* @__PURE__ */ l.createElement("path", {
fillRule: "evenodd",
d: "M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",
clipRule: "evenodd"
}),
play: /* @__PURE__ */ l.createElement("path", {
fillRule: "evenodd",
d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",
clipRule: "evenodd"
}),
pause: /* @__PURE__ */ l.createElement("path", {
fillRule: "evenodd",
d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",
clipRule: "evenodd"
}),
stop: /* @__PURE__ */ l.createElement("path", {
fillRule: "evenodd",
d: "M2 10a8 8 0 1116 0 8 8 0 01-16 0zm5-2.25A.75.75 0 017.75 7h4.5a.75.75 0 01.75.75v4.5a.75.75 0 01-.75.75h-4.5a.75.75 0 01-.75-.75v-4.5z",
clipRule: "evenodd"
})
}[e]);
}
const Ce = "_container_3i85k_1", Re = "_header_3i85k_5", Le = "_buttons_3i85k_9", qe = "_button_3i85k_9", xe = "_buttonDisabled_3i85k_17", ze = "_error_3i85k_21", He = "_body_3i85k_25", F = {
container: Ce,
header: Re,
buttons: Le,
button: qe,
buttonDisabled: xe,
error: ze,
body: He
}, Pe = () => fe().currentTime;
function Te({ tune: e, hideOutsideView: r = !1, enableKeyboard: t, drawTime: o, canvasHeight: u = 200 }) {
const {
code: m,
setCode: d,
evaluate: a,
activateCode: c,
error: h,
isDirty: g,
activeCode: n,
pattern: p,
started: b,
scheduler: q,
togglePlay: x,
stop: C,
canvasId: z,
id: E
} = Ne({
initialCode: e,
defaultOutput: de,
getTime: Pe,
evalOnMount: !!o,
drawContext: o ? (s) => document.querySelector("#" + s)?.getContext("2d") : null,
drawTime: o
}), [D, H] = _(), [B, N] = me({
threshold: 0.01
}), P = A(), R = $(() => ((N || !r) && (P.current = !0), N || P.current), [N, r]);
Fe({
view: D,
pattern: p,
active: b && !n?.includes("strudel disable-highlighting"),
getTime: () => q.now()
}), Y(() => {
if (t) {
const s = async (v) => {
(v.ctrlKey || v.altKey) && (v.code === "Enter" ? (v.preventDefault(), Ee(D), await c()) : v.code === "Period" && (C(), v.preventDefault()));
};
return window.addEventListener("keydown", s, !0), () => window.removeEventListener("keydown", s, !0);
}
}, [t, p, m, a, C, D]);
const [S, I] = _([]);
return Se(
w((s) => {
const { data: v } = s.detail;
v?.hap?.context?.id === E && I((V) => V.concat([s.detail]).slice(-10));
}, [])
), /* @__PURE__ */ l.createElement("div", {
className: F.container,
ref: B
}, /* @__PURE__ */ l.createElement("div", {
className: F.header
}, /* @__PURE__ */ l.createElement("div", {
className: F.buttons
}, /* @__PURE__ */ l.createElement("button", {
className: T(F.button, b ? "sc-animate-pulse" : ""),
onClick: () => x()
}, /* @__PURE__ */ l.createElement(X, {
type: b ? "stop" : "play"
})), /* @__PURE__ */ l.createElement("button", {
className: T(g ? F.button : F.buttonDisabled),
onClick: () => c()
}, /* @__PURE__ */ l.createElement(X, {
type: "refresh"
}))), h && /* @__PURE__ */ l.createElement("div", {
className: F.error
}, h.message)), /* @__PURE__ */ l.createElement("div", {
className: F.body
}, R && /* @__PURE__ */ l.createElement(ke, {
value: m,
onChange: d,
onViewChanged: H
})), o && /* @__PURE__ */ l.createElement("canvas", {
id: z,
className: "w-full pointer-events-none",
height: u,
ref: (s) => {
s && s.width !== s.clientWidth && (s.width = s.clientWidth);
}
}), !!S.length && /* @__PURE__ */ l.createElement("div", {
className: "sc-bg-gray-800 sc-rounded-md sc-p-2"
}, S.map(({ message: s }, v) => /* @__PURE__ */ l.createElement("div", {
key: v
}, s))));
}
function Se(e) {
Ve(ge.key, e);
}
function Ve(e, r, t = !1) {
k(() => (document.addEventListener(e, r, t), () => {
document.removeEventListener(e, r, t);
}), [r]);
}
const Xe = (e) => Y(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
export {
ke as CodeMirror,
Te as MiniRepl,
T as cx,
Ee as flash,
Fe as useHighlighting,
Xe as useKeydown,
Ae as usePostMessage,
Ne as useStrudel
};