fix: could play multiple mini repl at once

This commit is contained in:
Felix Roos 2022-11-17 10:07:19 +01:00
parent add9c43827
commit f1ae8a17cf
4 changed files with 179 additions and 149 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,15 +1,15 @@
import n, { useCallback as N, useRef as x, useEffect as R, useState as w, useMemo as I, useLayoutEffect as K } from "react"; import n, { useCallback as _, useRef as H, useEffect as L, useMemo as V, useState as w, useLayoutEffect as j } from "react";
import Q from "@uiw/react-codemirror"; import X from "@uiw/react-codemirror";
import { Decoration as E, EditorView as O } from "@codemirror/view"; import { Decoration as E, EditorView as U } from "@codemirror/view";
import { StateEffect as j, StateField as U } from "@codemirror/state"; import { StateEffect as $, StateField as G } from "@codemirror/state";
import { javascript as W } from "@codemirror/lang-javascript"; import { javascript as Y } from "@codemirror/lang-javascript";
import { tags as r } from "@lezer/highlight"; import { tags as r } from "@lezer/highlight";
import { createTheme as X } from "@uiw/codemirror-themes"; import { createTheme as Z } from "@uiw/codemirror-themes";
import { useInView as Y } from "react-hook-inview"; import { useInView as ee } from "react-hook-inview";
import { webaudioOutput as Z, getAudioContext as ee } from "@strudel.cycles/webaudio"; import { webaudioOutput as te, getAudioContext as re } from "@strudel.cycles/webaudio";
import { repl as te } from "@strudel.cycles/core"; import { repl as oe } from "@strudel.cycles/core";
import { transpiler as re } from "@strudel.cycles/transpiler"; import { transpiler as ne } from "@strudel.cycles/transpiler";
const oe = X({ const ae = Z({
theme: "dark", theme: "dark",
settings: { settings: {
background: "#222", background: "#222",
@ -42,14 +42,14 @@ const oe = X({
{ tag: r.invalid, color: "#ffffff" } { tag: r.invalid, color: "#ffffff" }
] ]
}); });
const T = j.define(), ne = U.define({ const B = $.define(), se = G.define({
create() { create() {
return E.none; return E.none;
}, },
update(e, t) { update(e, t) {
try { try {
for (let o of t.effects) for (let o of t.effects)
if (o.is(T)) if (o.is(B))
if (o.value) { if (o.value) {
const a = E.mark({ attributes: { style: "background-color: #FFCA2880" } }); const a = E.mark({ attributes: { style: "background-color: #FFCA2880" } });
e = E.set([a.range(0, t.newDoc.length)]); e = E.set([a.range(0, t.newDoc.length)]);
@ -60,25 +60,25 @@ const T = j.define(), ne = U.define({
return console.warn("flash error", o), e; return console.warn("flash error", o), e;
} }
}, },
provide: (e) => O.decorations.from(e) provide: (e) => U.decorations.from(e)
}), ae = (e) => { }), ce = (e) => {
e.dispatch({ effects: T.of(!0) }), setTimeout(() => { e.dispatch({ effects: B.of(!0) }), setTimeout(() => {
e.dispatch({ effects: T.of(!1) }); e.dispatch({ effects: B.of(!1) });
}, 200); }, 200);
}, A = j.define(), se = U.define({ }, z = $.define(), ie = G.define({
create() { create() {
return E.none; return E.none;
}, },
update(e, t) { update(e, t) {
try { try {
for (let o of t.effects) for (let o of t.effects)
if (o.is(A)) { if (o.is(z)) {
const a = o.value.map( const a = o.value.map(
(s) => (s.context.locations || []).map(({ start: m, end: l }) => { (s) => (s.context.locations || []).map(({ start: f, end: d }) => {
const d = s.context.color || "#FFCA28"; const u = s.context.color || "#FFCA28";
let c = t.newDoc.line(m.line).from + m.column, i = t.newDoc.line(l.line).from + l.column; let c = t.newDoc.line(f.line).from + f.column, i = t.newDoc.line(d.line).from + d.column;
const g = t.newDoc.length; const m = t.newDoc.length;
return c > g || i > g ? void 0 : E.mark({ attributes: { style: `outline: 1.5px solid ${d};` } }).range(c, i); return c > m || i > m ? void 0 : E.mark({ attributes: { style: `outline: 1.5px solid ${u};` } }).range(c, i);
}) })
).flat().filter(Boolean) || []; ).flat().filter(Boolean) || [];
e = E.set(a, !0); e = E.set(a, !0);
@ -88,69 +88,69 @@ const T = j.define(), ne = U.define({
return E.set([]); return E.set([]);
} }
}, },
provide: (e) => O.decorations.from(e) provide: (e) => U.decorations.from(e)
}), ce = [W(), oe, se, ne]; }), le = [Y(), ae, ie, se];
function ie({ value: e, onChange: t, onViewChanged: o, onSelectionChange: a, options: s, editorDidMount: m }) { function de({ value: e, onChange: t, onViewChanged: o, onSelectionChange: a, options: s, editorDidMount: f }) {
const l = N( const d = _(
(i) => { (i) => {
t?.(i); t?.(i);
}, },
[t] [t]
), d = N( ), u = _(
(i) => { (i) => {
o?.(i); o?.(i);
}, },
[o] [o]
), c = N( ), c = _(
(i) => { (i) => {
i.selectionSet && a && a?.(i.state.selection); i.selectionSet && a && a?.(i.state.selection);
}, },
[a] [a]
); );
return /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement(Q, { return /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement(X, {
value: e, value: e,
onChange: l, onChange: d,
onCreateEditor: d, onCreateEditor: u,
onUpdate: c, onUpdate: c,
extensions: ce extensions: le
})); }));
} }
function B(...e) { function K(...e) {
return e.filter(Boolean).join(" "); return e.filter(Boolean).join(" ");
} }
function le({ view: e, pattern: t, active: o, getTime: a }) { function ue({ view: e, pattern: t, active: o, getTime: a }) {
const s = x([]), m = x(); const s = H([]), f = H();
R(() => { L(() => {
if (e) if (e)
if (t && o) { if (t && o) {
let d = function() { let u = function() {
try { try {
const c = a(), g = [Math.max(m.current || c, c - 1 / 10, 0), c + 1 / 60]; const c = a(), m = [Math.max(f.current || c, c - 1 / 10, 0), c + 1 / 60];
m.current = g[1], s.current = s.current.filter((p) => p.whole.end > c); f.current = m[1], s.current = s.current.filter((g) => g.whole.end > c);
const h = t.queryArc(...g).filter((p) => p.hasOnset()); const h = t.queryArc(...m).filter((g) => g.hasOnset());
s.current = s.current.concat(h), e.dispatch({ effects: A.of(s.current) }); s.current = s.current.concat(h), e.dispatch({ effects: z.of(s.current) });
} catch { } catch {
e.dispatch({ effects: A.of([]) }); e.dispatch({ effects: z.of([]) });
} }
l = requestAnimationFrame(d); d = requestAnimationFrame(u);
}, l = requestAnimationFrame(d); }, d = requestAnimationFrame(u);
return () => { return () => {
cancelAnimationFrame(l); cancelAnimationFrame(d);
}; };
} else } else
s.current = [], e.dispatch({ effects: A.of([]) }); s.current = [], e.dispatch({ effects: z.of([]) });
}, [t, o, e]); }, [t, o, e]);
} }
const de = "_container_3i85k_1", ue = "_header_3i85k_5", fe = "_buttons_3i85k_9", me = "_button_3i85k_9", ge = "_buttonDisabled_3i85k_17", pe = "_error_3i85k_21", he = "_body_3i85k_25", b = { const fe = "_container_3i85k_1", me = "_header_3i85k_5", ge = "_buttons_3i85k_9", pe = "_button_3i85k_9", he = "_buttonDisabled_3i85k_17", be = "_error_3i85k_21", ve = "_body_3i85k_25", v = {
container: de, container: fe,
header: ue, header: me,
buttons: fe, buttons: ge,
button: me, button: pe,
buttonDisabled: ge, buttonDisabled: he,
error: pe, error: be,
body: he body: ve
}; };
function q({ type: e }) { function O({ type: e }) {
return /* @__PURE__ */ n.createElement("svg", { return /* @__PURE__ */ n.createElement("svg", {
xmlns: "http://www.w3.org/2000/svg", xmlns: "http://www.w3.org/2000/svg",
className: "sc-h-5 sc-w-5", className: "sc-h-5 sc-w-5",
@ -174,137 +174,147 @@ function q({ type: e }) {
}) })
}[e]); }[e]);
} }
function ve({ function Ee(e) {
return L(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), _((t) => window.postMessage(t, "*"), []);
}
function we({
defaultOutput: e, defaultOutput: e,
interval: t, interval: t,
getTime: o, getTime: o,
evalOnMount: a = !1, evalOnMount: a = !1,
initialCode: s = "", initialCode: s = "",
autolink: m = !1, autolink: f = !1,
beforeEval: l, beforeEval: d,
afterEval: d, afterEval: u,
onEvalError: c, onEvalError: c,
onToggle: i onToggle: i
}) { }) {
const [g, h] = w(), [p, D] = w(), [v, k] = w(s), [y, P] = w(), [z, _] = w(), [C, H] = w(!1), F = v !== y, { scheduler: u, evaluate: L, start: $, stop: G, pause: J } = I( const m = V(() => ye(), []), [h, g] = w(), [C, N] = w(), [p, y] = w(s), [M, S] = w(), [k, D] = w(), [F, x] = w(!1), b = p !== M, { scheduler: A, evaluate: T, start: J, stop: q, pause: Q } = V(
() => te({ () => oe({
interval: t, interval: t,
defaultOutput: e, defaultOutput: e,
onSchedulerError: h, onSchedulerError: g,
onEvalError: (f) => { onEvalError: (l) => {
D(f), c?.(f); N(l), c?.(l);
}, },
getTime: o, getTime: o,
transpiler: re, transpiler: ne,
beforeEval: ({ code: f }) => { beforeEval: ({ code: l }) => {
k(f), l?.(); y(l), d?.();
}, },
afterEval: ({ pattern: f, code: S }) => { afterEval: ({ pattern: l, code: P }) => {
P(S), _(f), D(), h(), m && (window.location.hash = "#" + encodeURIComponent(btoa(S))), d?.(); S(P), D(l), N(), g(), f && (window.location.hash = "#" + encodeURIComponent(btoa(P))), u?.();
}, },
onToggle: (f) => { onToggle: (l) => {
H(f), i?.(f); x(l), i?.(l);
} }
}), }),
[e, t, o] [e, t, o]
), M = N(async (f = !0) => L(v, f), [L, v]), V = x(); ), W = Ee(({ data: { from: l, type: P } }) => {
return R(() => { P === "start" && l !== m && q();
!V.current && a && v && (V.current = !0, M()); }), R = _(
}, [M, a, v]), R(() => () => { async (l = !0) => {
u.stop(); await T(p, l), W({ type: "start", from: m });
}, [u]), { },
code: v, [T, p]
setCode: k, ), I = H();
error: g || p, return L(() => {
schedulerError: g, !I.current && a && p && (I.current = !0, R());
scheduler: u, }, [R, a, p]), L(() => () => {
evalError: p, A.stop();
evaluate: L, }, [A]), {
activateCode: M, code: p,
activeCode: y, setCode: y,
isDirty: F, error: h || C,
pattern: z, schedulerError: h,
started: C, scheduler: A,
start: $, evalError: C,
stop: G, evaluate: T,
pause: J, activateCode: R,
activeCode: M,
isDirty: b,
pattern: k,
started: F,
start: J,
stop: q,
pause: Q,
togglePlay: async () => { togglePlay: async () => {
C ? u.pause() : await M(); F ? A.pause() : await R();
} }
}; };
} }
const be = () => ee().currentTime; function ye() {
function Pe({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) { return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
}
const ke = () => re().currentTime;
function Se({ tune: e, hideOutsideView: t = !1, init: o, enableKeyboard: a }) {
const { const {
code: s, code: s,
setCode: m, setCode: f,
evaluate: l, evaluate: d,
activateCode: d, activateCode: u,
error: c, error: c,
isDirty: i, isDirty: i,
activeCode: g, activeCode: m,
pattern: h, pattern: h,
started: p, started: g,
scheduler: D, scheduler: C,
togglePlay: v, togglePlay: N,
stop: k stop: p
} = ve({ } = we({
initialCode: e, initialCode: e,
defaultOutput: Z, defaultOutput: te,
getTime: be getTime: ke
}), [y, P] = w(), [z, _] = Y({ }), [y, M] = w(), [S, k] = ee({
threshold: 0.01 threshold: 0.01
}), C = x(), H = I(() => ((_ || !t) && (C.current = !0), _ || C.current), [_, t]); }), D = H(), F = V(() => ((k || !t) && (D.current = !0), k || D.current), [k, t]);
return le({ return ue({
view: y, view: y,
pattern: h, pattern: h,
active: p && !g?.includes("strudel disable-highlighting"), active: g && !m?.includes("strudel disable-highlighting"),
getTime: () => D.getPhase() getTime: () => C.getPhase()
}), K(() => { }), j(() => {
if (a) { if (a) {
const F = async (u) => { const x = async (b) => {
(u.ctrlKey || u.altKey) && (u.code === "Enter" ? (u.preventDefault(), ae(y), await d()) : u.code === "Period" && (k(), u.preventDefault())); (b.ctrlKey || b.altKey) && (b.code === "Enter" ? (b.preventDefault(), ce(y), await u()) : b.code === "Period" && (p(), b.preventDefault()));
}; };
return window.addEventListener("keydown", F, !0), () => window.removeEventListener("keydown", F, !0); return window.addEventListener("keydown", x, !0), () => window.removeEventListener("keydown", x, !0);
} }
}, [a, h, s, l, k, y]), /* @__PURE__ */ n.createElement("div", { }, [a, h, s, d, p, y]), /* @__PURE__ */ n.createElement("div", {
className: b.container, className: v.container,
ref: z ref: S
}, /* @__PURE__ */ n.createElement("div", { }, /* @__PURE__ */ n.createElement("div", {
className: b.header className: v.header
}, /* @__PURE__ */ n.createElement("div", { }, /* @__PURE__ */ n.createElement("div", {
className: b.buttons className: v.buttons
}, /* @__PURE__ */ n.createElement("button", { }, /* @__PURE__ */ n.createElement("button", {
className: B(b.button, p ? "sc-animate-pulse" : ""), className: K(v.button, g ? "sc-animate-pulse" : ""),
onClick: () => v() onClick: () => N()
}, /* @__PURE__ */ n.createElement(q, { }, /* @__PURE__ */ n.createElement(O, {
type: p ? "pause" : "play" type: g ? "pause" : "play"
})), /* @__PURE__ */ n.createElement("button", { })), /* @__PURE__ */ n.createElement("button", {
className: B(i ? b.button : b.buttonDisabled), className: K(i ? v.button : v.buttonDisabled),
onClick: () => d() onClick: () => u()
}, /* @__PURE__ */ n.createElement(q, { }, /* @__PURE__ */ n.createElement(O, {
type: "refresh" type: "refresh"
}))), c && /* @__PURE__ */ n.createElement("div", { }))), c && /* @__PURE__ */ n.createElement("div", {
className: b.error className: v.error
}, c.message)), /* @__PURE__ */ n.createElement("div", { }, c.message)), /* @__PURE__ */ n.createElement("div", {
className: b.body className: v.body
}, H && /* @__PURE__ */ n.createElement(ie, { }, F && /* @__PURE__ */ n.createElement(de, {
value: s, value: s,
onChange: m, onChange: f,
onViewChanged: P onViewChanged: M
}))); })));
} }
function ze(e) { const Te = (e) => j(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
return R(() => (window.addEventListener("message", e), () => window.removeEventListener("message", e)), [e]), N((t) => window.postMessage(t, "*"), []);
}
const He = (e) => K(() => (window.addEventListener("keydown", e, !0), () => window.removeEventListener("keydown", e, !0)), [e]);
export { export {
ie as CodeMirror, de as CodeMirror,
Pe as MiniRepl, Se as MiniRepl,
B as cx, K as cx,
ae as flash, ce as flash,
le as useHighlighting, ue as useHighlighting,
He as useKeydown, Te as useKeydown,
ze as usePostMessage, Ee as usePostMessage,
ve as useStrudel we as useStrudel
}; };

View File

@ -1,6 +1,7 @@
import { useRef, useCallback, useEffect, useMemo, useState } from 'react'; import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
import { repl } from '@strudel.cycles/core'; import { repl } from '@strudel.cycles/core';
import { transpiler } from '@strudel.cycles/transpiler'; import { transpiler } from '@strudel.cycles/transpiler';
import usePostMessage from './usePostMessage.mjs';
function useStrudel({ function useStrudel({
defaultOutput, defaultOutput,
@ -14,6 +15,7 @@ function useStrudel({
onEvalError, onEvalError,
onToggle, onToggle,
}) { }) {
const id = useMemo(() => s4(), []);
// scheduler // scheduler
const [schedulerError, setSchedulerError] = useState(); const [schedulerError, setSchedulerError] = useState();
const [evalError, setEvalError] = useState(); const [evalError, setEvalError] = useState();
@ -57,7 +59,19 @@ function useStrudel({
}), }),
[defaultOutput, interval, getTime], [defaultOutput, interval, getTime],
); );
const activateCode = useCallback(async (autostart = true) => evaluate(code, autostart), [evaluate, code]); const broadcast = usePostMessage(({ data: { from, type } }) => {
if (type === 'start' && from !== id) {
// console.log('message', from, type);
stop();
}
});
const activateCode = useCallback(
async (autostart = true) => {
await evaluate(code, autostart);
broadcast({ type: 'start', from: id });
},
[evaluate, code],
);
const inited = useRef(); const inited = useRef();
useEffect(() => { useEffect(() => {
@ -103,3 +117,9 @@ function useStrudel({
} }
export default useStrudel; export default useStrudel;
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}

View File

@ -19,7 +19,7 @@ evalScope(
import('@strudel.cycles/osc'), import('@strudel.cycles/osc'),
); );
prebake(); // prebake();
export function MiniRepl({ tune }) { export function MiniRepl({ tune }) {
return <_MiniRepl tune={tune} hideOutsideView={true} />; return <_MiniRepl tune={tune} hideOutsideView={true} />;