mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-12 06:08:37 +00:00
379 lines
12 KiB
JavaScript
379 lines
12 KiB
JavaScript
import i, { useCallback as N, useRef as E, useEffect as F, useMemo as V, useState as _, useLayoutEffect as U } from "react";
|
|
import Y from "@uiw/react-codemirror";
|
|
import { Decoration as y, EditorView as W } from "@codemirror/view";
|
|
import { StateEffect as $, StateField as G } from "@codemirror/state";
|
|
import { javascript as Z } from "@codemirror/lang-javascript";
|
|
import { tags as s } from "@lezer/highlight";
|
|
import { createTheme as ee } from "@uiw/codemirror-themes";
|
|
import { repl as te, pianoroll as re } from "@strudel.cycles/core";
|
|
import { webaudioOutput as oe, getAudioContext as ne } from "@strudel.cycles/webaudio";
|
|
import { useInView as ae } from "react-hook-inview";
|
|
import { transpiler as se } from "@strudel.cycles/transpiler";
|
|
const ce = ee({
|
|
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: s.keyword, color: "#c792ea" },
|
|
{ tag: s.operator, color: "#89ddff" },
|
|
{ tag: s.special(s.variableName), color: "#eeffff" },
|
|
{ tag: s.typeName, color: "#c3e88d" },
|
|
{ tag: s.atom, color: "#f78c6c" },
|
|
{ tag: s.number, color: "#c3e88d" },
|
|
{ tag: s.definition(s.variableName), color: "#82aaff" },
|
|
{ tag: s.string, color: "#c3e88d" },
|
|
{ tag: s.special(s.string), color: "#c3e88d" },
|
|
{ tag: s.comment, color: "#7d8799" },
|
|
{ tag: s.variableName, color: "#c792ea" },
|
|
{ tag: s.tagName, color: "#c3e88d" },
|
|
{ tag: s.bracket, color: "#525154" },
|
|
{ tag: s.meta, color: "#ffcb6b" },
|
|
{ tag: s.attributeName, color: "#c792ea" },
|
|
{ tag: s.propertyName, color: "#c792ea" },
|
|
{ tag: s.className, color: "#decb6b" },
|
|
{ tag: s.invalid, color: "#ffffff" }
|
|
]
|
|
});
|
|
const B = $.define(), ie = G.define({
|
|
create() {
|
|
return y.none;
|
|
},
|
|
update(e, r) {
|
|
try {
|
|
for (let t of r.effects)
|
|
if (t.is(B))
|
|
if (t.value) {
|
|
const a = y.mark({ attributes: { style: "background-color: #FFCA2880" } });
|
|
e = y.set([a.range(0, r.newDoc.length)]);
|
|
} else
|
|
e = y.set([]);
|
|
return e;
|
|
} catch (t) {
|
|
return console.warn("flash error", t), e;
|
|
}
|
|
},
|
|
provide: (e) => W.decorations.from(e)
|
|
}), le = (e) => {
|
|
e.dispatch({ effects: B.of(!0) }), setTimeout(() => {
|
|
e.dispatch({ effects: B.of(!1) });
|
|
}, 200);
|
|
}, H = $.define(), ue = G.define({
|
|
create() {
|
|
return y.none;
|
|
},
|
|
update(e, r) {
|
|
try {
|
|
for (let t of r.effects)
|
|
if (t.is(H)) {
|
|
const a = t.value.map(
|
|
(n) => (n.context.locations || []).map(({ start: c, end: l }) => {
|
|
const d = n.context.color || "#FFCA28";
|
|
let o = r.newDoc.line(c.line).from + c.column, u = r.newDoc.line(l.line).from + l.column;
|
|
const f = r.newDoc.length;
|
|
return o > f || u > f ? void 0 : y.mark({ attributes: { style: `outline: 1.5px solid ${d};` } }).range(o, u);
|
|
})
|
|
).flat().filter(Boolean) || [];
|
|
e = y.set(a, !0);
|
|
}
|
|
return e;
|
|
} catch {
|
|
return y.set([]);
|
|
}
|
|
},
|
|
provide: (e) => W.decorations.from(e)
|
|
}), de = [Z(), ce, ue, ie];
|
|
function fe({ value: e, onChange: r, onViewChanged: t, onSelectionChange: a, options: n, editorDidMount: c }) {
|
|
const l = N(
|
|
(u) => {
|
|
r?.(u);
|
|
},
|
|
[r]
|
|
), d = N(
|
|
(u) => {
|
|
t?.(u);
|
|
},
|
|
[t]
|
|
), o = N(
|
|
(u) => {
|
|
u.selectionSet && a && a?.(u.state.selection);
|
|
},
|
|
[a]
|
|
);
|
|
return /* @__PURE__ */ i.createElement(i.Fragment, null, /* @__PURE__ */ i.createElement(Y, {
|
|
value: e,
|
|
onChange: l,
|
|
onCreateEditor: d,
|
|
onUpdate: o,
|
|
extensions: de
|
|
}));
|
|
}
|
|
function j(...e) {
|
|
return e.filter(Boolean).join(" ");
|
|
}
|
|
function me({ view: e, pattern: r, active: t, getTime: a }) {
|
|
const n = E([]), c = E();
|
|
F(() => {
|
|
if (e)
|
|
if (r && t) {
|
|
let l = requestAnimationFrame(function d() {
|
|
try {
|
|
const o = a(), f = [Math.max(c.current || o, o - 1 / 10, 0), o + 1 / 60];
|
|
c.current = f[1], n.current = n.current.filter((v) => v.whole.end > o);
|
|
const m = r.queryArc(...f).filter((v) => v.hasOnset());
|
|
n.current = n.current.concat(m), e.dispatch({ effects: H.of(n.current) });
|
|
} catch {
|
|
e.dispatch({ effects: H.of([]) });
|
|
}
|
|
l = requestAnimationFrame(d);
|
|
});
|
|
return () => {
|
|
cancelAnimationFrame(l);
|
|
};
|
|
} else
|
|
n.current = [], e.dispatch({ effects: H.of([]) });
|
|
}, [r, t, e]);
|
|
}
|
|
function ge(e, r = !1) {
|
|
const t = E(), a = E(), n = (d) => {
|
|
if (a.current !== void 0) {
|
|
const o = d - a.current;
|
|
e(d, o);
|
|
}
|
|
a.current = d, t.current = requestAnimationFrame(n);
|
|
}, c = () => {
|
|
t.current = requestAnimationFrame(n);
|
|
}, l = () => {
|
|
t.current && cancelAnimationFrame(t.current), delete t.current;
|
|
};
|
|
return F(() => {
|
|
t.current && (l(), c());
|
|
}, [e]), F(() => (r && c(), l), []), {
|
|
start: c,
|
|
stop: l
|
|
};
|
|
}
|
|
function pe({ pattern: e, started: r, getTime: t, onDraw: a }) {
|
|
let n = E([]), c = E(null);
|
|
const { start: l, stop: d } = ge(
|
|
N(() => {
|
|
const o = t();
|
|
if (c.current === null) {
|
|
c.current = o;
|
|
return;
|
|
}
|
|
const u = e.queryArc(Math.max(c.current, o - 1 / 10), o), f = 4;
|
|
c.current = o, n.current = (n.current || []).filter((m) => m.whole.end > o - f).concat(u.filter((m) => m.hasOnset())), a(o, n.current);
|
|
}, [e])
|
|
);
|
|
F(() => {
|
|
r ? l() : (n.current = [], d());
|
|
}, [r]);
|
|
}
|
|
function he(e) {
|
|
return F(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), N((r) => window.postMessage(r, "*"), []);
|
|
}
|
|
function ve({
|
|
defaultOutput: e,
|
|
interval: r,
|
|
getTime: t,
|
|
evalOnMount: a = !1,
|
|
initialCode: n = "",
|
|
autolink: c = !1,
|
|
beforeEval: l,
|
|
afterEval: d,
|
|
onEvalError: o,
|
|
onToggle: u,
|
|
canvasId: f
|
|
}) {
|
|
const m = V(() => be(), []);
|
|
f = f || `canvas-${m}`;
|
|
const [v, k] = _(), [M, T] = _(), [b, A] = _(n), [C, S] = _(), [z, D] = _(), [x, L] = _(!1), h = b !== C, { scheduler: g, evaluate: R, start: J, stop: O, pause: Q } = V(
|
|
() => te({
|
|
interval: r,
|
|
defaultOutput: e,
|
|
onSchedulerError: k,
|
|
onEvalError: (p) => {
|
|
T(p), o?.(p);
|
|
},
|
|
getTime: t,
|
|
transpiler: se,
|
|
beforeEval: ({ code: p }) => {
|
|
A(p), l?.();
|
|
},
|
|
afterEval: ({ pattern: p, code: q }) => {
|
|
S(q), D(p), T(), k(), c && (window.location.hash = "#" + encodeURIComponent(btoa(q))), d?.();
|
|
},
|
|
onToggle: (p) => {
|
|
L(p), u?.(p);
|
|
}
|
|
}),
|
|
[e, r, t]
|
|
), X = he(({ data: { from: p, type: q } }) => {
|
|
q === "start" && p !== m && O();
|
|
}), P = N(
|
|
async (p = !0) => {
|
|
await R(b, p), X({ type: "start", from: m });
|
|
},
|
|
[R, b]
|
|
), K = E();
|
|
return F(() => {
|
|
!K.current && a && b && (K.current = !0, P());
|
|
}, [P, a, b]), F(() => () => {
|
|
g.stop();
|
|
}, [g]), {
|
|
id: m,
|
|
canvasId: f,
|
|
code: b,
|
|
setCode: A,
|
|
error: v || M,
|
|
schedulerError: v,
|
|
scheduler: g,
|
|
evalError: M,
|
|
evaluate: R,
|
|
activateCode: P,
|
|
activeCode: C,
|
|
isDirty: h,
|
|
pattern: z,
|
|
started: x,
|
|
start: J,
|
|
stop: O,
|
|
pause: Q,
|
|
togglePlay: async () => {
|
|
x ? g.pause() : await P();
|
|
}
|
|
};
|
|
}
|
|
function be() {
|
|
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
|
|
}
|
|
function I({ type: e }) {
|
|
return /* @__PURE__ */ i.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__ */ i.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__ */ i.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__ */ i.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"
|
|
})
|
|
}[e]);
|
|
}
|
|
const we = "_container_3i85k_1", ye = "_header_3i85k_5", Ee = "_buttons_3i85k_9", ke = "_button_3i85k_9", _e = "_buttonDisabled_3i85k_17", Fe = "_error_3i85k_21", Ce = "_body_3i85k_25", w = {
|
|
container: we,
|
|
header: ye,
|
|
buttons: Ee,
|
|
button: ke,
|
|
buttonDisabled: _e,
|
|
error: Fe,
|
|
body: Ce
|
|
}, Ne = () => ne().currentTime;
|
|
function Be({ tune: e, hideOutsideView: r = !1, enableKeyboard: t, withCanvas: a = !1, canvasHeight: n = 200 }) {
|
|
const {
|
|
code: c,
|
|
setCode: l,
|
|
evaluate: d,
|
|
activateCode: o,
|
|
error: u,
|
|
isDirty: f,
|
|
activeCode: m,
|
|
pattern: v,
|
|
started: k,
|
|
scheduler: M,
|
|
togglePlay: T,
|
|
stop: b,
|
|
canvasId: A
|
|
} = ve({
|
|
initialCode: e,
|
|
defaultOutput: oe,
|
|
getTime: Ne
|
|
});
|
|
pe({
|
|
pattern: v,
|
|
started: a && k,
|
|
getTime: () => M.now(),
|
|
onDraw: (h, g) => {
|
|
const R = document.querySelector("#" + A).getContext("2d");
|
|
re({ ctx: R, time: h, haps: g, autorange: 1, fold: 1, playhead: 1 });
|
|
}
|
|
});
|
|
const [C, S] = _(), [z, D] = ae({
|
|
threshold: 0.01
|
|
}), x = E(), L = V(() => ((D || !r) && (x.current = !0), D || x.current), [D, r]);
|
|
return me({
|
|
view: C,
|
|
pattern: v,
|
|
active: k && !m?.includes("strudel disable-highlighting"),
|
|
getTime: () => M.getPhase()
|
|
}), U(() => {
|
|
if (t) {
|
|
const h = async (g) => {
|
|
(g.ctrlKey || g.altKey) && (g.code === "Enter" ? (g.preventDefault(), le(C), await o()) : g.code === "Period" && (b(), g.preventDefault()));
|
|
};
|
|
return window.addEventListener("keydown", h, !0), () => window.removeEventListener("keydown", h, !0);
|
|
}
|
|
}, [t, v, c, d, b, C]), /* @__PURE__ */ i.createElement("div", {
|
|
className: w.container,
|
|
ref: z
|
|
}, /* @__PURE__ */ i.createElement("div", {
|
|
className: w.header
|
|
}, /* @__PURE__ */ i.createElement("div", {
|
|
className: w.buttons
|
|
}, /* @__PURE__ */ i.createElement("button", {
|
|
className: j(w.button, k ? "sc-animate-pulse" : ""),
|
|
onClick: () => T()
|
|
}, /* @__PURE__ */ i.createElement(I, {
|
|
type: k ? "pause" : "play"
|
|
})), /* @__PURE__ */ i.createElement("button", {
|
|
className: j(f ? w.button : w.buttonDisabled),
|
|
onClick: () => o()
|
|
}, /* @__PURE__ */ i.createElement(I, {
|
|
type: "refresh"
|
|
}))), u && /* @__PURE__ */ i.createElement("div", {
|
|
className: w.error
|
|
}, u.message)), /* @__PURE__ */ i.createElement("div", {
|
|
className: w.body
|
|
}, L && /* @__PURE__ */ i.createElement(fe, {
|
|
value: c,
|
|
onChange: l,
|
|
onViewChanged: S
|
|
})), a && /* @__PURE__ */ i.createElement("canvas", {
|
|
id: A,
|
|
className: "w-full pointer-events-none",
|
|
height: n,
|
|
ref: (h) => {
|
|
h && h.width !== h.clientWidth && (h.width = h.clientWidth);
|
|
}
|
|
}));
|
|
}
|
|
const Oe = (e) => U(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
|
|
export {
|
|
fe as CodeMirror,
|
|
Be as MiniRepl,
|
|
j as cx,
|
|
le as flash,
|
|
me as useHighlighting,
|
|
Oe as useKeydown,
|
|
he as usePostMessage,
|
|
ve as useStrudel
|
|
};
|