diff --git a/packages/react/src/components/CodeMirror6.jsx b/packages/react/src/components/CodeMirror6.jsx index 1dbc1066..eb02004a 100644 --- a/packages/react/src/components/CodeMirror6.jsx +++ b/packages/react/src/components/CodeMirror6.jsx @@ -49,11 +49,12 @@ const highlightField = StateField.define({ try { for (let e of tr.effects) { if (e.is(setHighlights)) { + const { haps } = e.value; const marks = - e.value + haps .map((hap) => (hap.context.locations || []).map(({ start, end }) => { - const color = hap.context.color || '#FFCA28'; + const color = hap.context.color || e.value.color || '#FFCA28'; let from = tr.newDoc.line(start.line).from + start.column; let to = tr.newDoc.line(end.line).from + end.column; const l = tr.newDoc.length; diff --git a/packages/react/src/components/MiniRepl.jsx b/packages/react/src/components/MiniRepl.jsx index 4649969a..508353e4 100644 --- a/packages/react/src/components/MiniRepl.jsx +++ b/packages/react/src/components/MiniRepl.jsx @@ -10,6 +10,7 @@ import { Icon } from './Icon'; import styles from './MiniRepl.module.css'; import './style.css'; import { logger } from '@strudel.cycles/core'; +import useEvent from '../hooks/useEvent.mjs'; const getTime = () => getAudioContext().currentTime; @@ -21,6 +22,7 @@ export function MiniRepl({ punchcard, canvasHeight = 200, theme, + highlightColor, }) { drawTime = drawTime || (punchcard ? [0, 4] : undefined); const evalOnMount = !!drawTime; @@ -69,6 +71,7 @@ export function MiniRepl({ pattern, active: started && !activeCode?.includes('strudel disable-highlighting'), getTime: () => scheduler.now(), + color: highlightColor, }); // set active pattern on ctrl+enter @@ -148,13 +151,3 @@ export function MiniRepl({ function useLogger(onTrigger) { useEvent(logger.key, onTrigger); } - -// TODO: dedupe -function useEvent(name, onTrigger, useCapture = false) { - useEffect(() => { - document.addEventListener(name, onTrigger, useCapture); - return () => { - document.removeEventListener(name, onTrigger, useCapture); - }; - }, [onTrigger]); -} diff --git a/packages/react/src/hooks/useEvent.mjs b/packages/react/src/hooks/useEvent.mjs new file mode 100644 index 00000000..f349c6e7 --- /dev/null +++ b/packages/react/src/hooks/useEvent.mjs @@ -0,0 +1,12 @@ +import { useEffect } from 'react'; + +function useEvent(name, onTrigger, useCapture = false) { + useEffect(() => { + document.addEventListener(name, onTrigger, useCapture); + return () => { + document.removeEventListener(name, onTrigger, useCapture); + }; + }, [onTrigger]); +} + +export default useEvent; diff --git a/packages/react/src/hooks/useHighlighting.mjs b/packages/react/src/hooks/useHighlighting.mjs index 1b7a2ced..84262583 100644 --- a/packages/react/src/hooks/useHighlighting.mjs +++ b/packages/react/src/hooks/useHighlighting.mjs @@ -1,7 +1,7 @@ import { useEffect, useRef } from 'react'; import { setHighlights } from '../components/CodeMirror6'; -function useHighlighting({ view, pattern, active, getTime }) { +function useHighlighting({ view, pattern, active, getTime, color }) { const highlights = useRef([]); const lastEnd = useRef(0); useEffect(() => { @@ -19,9 +19,9 @@ function useHighlighting({ view, pattern, active, getTime }) { highlights.current = highlights.current.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset()); highlights.current = highlights.current.concat(haps); // add potential new onsets - view.dispatch({ effects: setHighlights.of(highlights.current) }); // highlight all still active + new active haps + view.dispatch({ effects: setHighlights.of({ haps: highlights.current, color }) }); // highlight all still active + new active haps } catch (err) { - view.dispatch({ effects: setHighlights.of([]) }); + view.dispatch({ effects: setHighlights.of({ haps: [] }) }); } frame = requestAnimationFrame(updateHighlights); }); @@ -30,10 +30,10 @@ function useHighlighting({ view, pattern, active, getTime }) { }; } else { highlights.current = []; - view.dispatch({ effects: setHighlights.of([]) }); + view.dispatch({ effects: setHighlights.of({ haps: [] }) }); } } - }, [pattern, active, view]); + }, [pattern, active, view, color]); } export default useHighlighting; diff --git a/packages/react/src/index.js b/packages/react/src/index.js index 40b9fa8d..f9eca1cd 100644 --- a/packages/react/src/index.js +++ b/packages/react/src/index.js @@ -1,10 +1,11 @@ // import 'tailwindcss/tailwind.css'; -export { default as CodeMirror, flash } from './components/CodeMirror6'; -export * from './components/MiniRepl'; -export { default as useHighlighting } from './hooks/useHighlighting'; +export { default as CodeMirror, flash } from './components/CodeMirror6'; // !SSR +export * from './components/MiniRepl'; // !SSR +export { default as useHighlighting } from './hooks/useHighlighting'; // !SSR +export { default as useStrudel } from './hooks/useStrudel'; // !SSR export { default as usePostMessage } from './hooks/usePostMessage'; -export { default as useStrudel } from './hooks/useStrudel'; export { default as useKeydown } from './hooks/useKeydown'; +export { default as useEvent } from './hooks/useEvent'; export { default as strudelTheme } from './themes/strudel-theme'; export { default as cx } from './cx'; diff --git a/website/src/docs/MiniRepl.jsx b/website/src/docs/MiniRepl.jsx index 33aa4523..f3e83d99 100644 --- a/website/src/docs/MiniRepl.jsx +++ b/website/src/docs/MiniRepl.jsx @@ -2,7 +2,7 @@ import { evalScope, controls } from '@strudel.cycles/core'; import { initAudioOnFirstClick } from '@strudel.cycles/webaudio'; import { useEffect, useState } from 'react'; import { prebake } from '../repl/prebake'; -import { themes } from '../repl/themes.mjs'; +import { themes, settings } from '../repl/themes.mjs'; import './MiniRepl.css'; const theme = localStorage.getItem('strudel-theme') || 'strudelTheme'; @@ -36,6 +36,7 @@ export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) { .then(([res]) => setRepl(() => res.MiniRepl)) .catch((err) => console.error(err)); }, []); + // const { settings } = useTheme(); return Repl ? (
) : ( diff --git a/website/src/repl/Footer.jsx b/website/src/repl/Footer.jsx index 26104e6f..28939c03 100644 --- a/website/src/repl/Footer.jsx +++ b/website/src/repl/Footer.jsx @@ -1,9 +1,10 @@ import XMarkIcon from '@heroicons/react/20/solid/XMarkIcon'; import { logger } from '@strudel.cycles/core'; -import { cx } from '@strudel.cycles/react'; +import { useEvent, cx } from '@strudel.cycles/react'; +// import { cx } from '@strudel.cycles/react'; import { nanoid } from 'nanoid'; import React, { useCallback, useLayoutEffect, useRef, useState } from 'react'; -import { useEvent, loadedSamples } from './Repl'; +import { loadedSamples } from './Repl'; import { Reference } from './Reference'; import { themes, themeColors } from './themes.mjs'; @@ -172,7 +173,7 @@ export function Footer({ context }) { {Object.entries(themes).map(([k, t]) => (
{ - console.log(e.detail); - setTheme(e.detail); - }); - const themeSettings = settings[theme || 'strudelTheme']; - return { - theme, - setTheme, - settings: themeSettings, - isDark: !themeSettings.light, - isLight: !!themeSettings.light, - }; -} - */ diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index b49722bc..818adea9 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th */ import { cleanupDraw, cleanupUi, controls, evalScope, getDrawContext, logger } from '@strudel.cycles/core'; -import { CodeMirror, cx, flash, useHighlighting, useStrudel } from '@strudel.cycles/react'; +import { CodeMirror, cx, flash, useHighlighting, useStrudel, useKeydown } from '@strudel.cycles/react'; import { getAudioContext, getLoadedSamples, @@ -23,6 +23,7 @@ import { prebake } from './prebake.mjs'; import * as tunes from './tunes.mjs'; import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; import { themes } from './themes.mjs'; +import useTheme from '../useTheme'; const initialTheme = localStorage.getItem('strudel-theme') || 'strudelTheme'; @@ -171,12 +172,15 @@ export function Repl({ embedded = false }) { ), ); + const { settings } = useTheme(); + // highlighting useHighlighting({ view, pattern, active: started && !activeCode?.includes('strudel disable-highlighting'), getTime: () => scheduler.now(), + color: settings?.foreground, }); // @@ -299,15 +303,3 @@ export function Repl({ embedded = false }) { ); } - -export function useEvent(name, onTrigger, useCapture = false) { - useEffect(() => { - document.addEventListener(name, onTrigger, useCapture); - return () => { - document.removeEventListener(name, onTrigger, useCapture); - }; - }, [onTrigger]); -} -function useKeydown(onTrigger) { - useEvent('keydown', onTrigger, true); -} diff --git a/website/src/useTheme.jsx b/website/src/useTheme.jsx new file mode 100644 index 00000000..51c66cbf --- /dev/null +++ b/website/src/useTheme.jsx @@ -0,0 +1,27 @@ +import { useState } from 'react'; +import { settings } from './repl/themes.mjs'; +import { useEffect } from 'react'; + +function useTheme() { + const [theme, setTheme] = useState(localStorage.getItem('strudel-theme')); + useEvent('strudel-theme', (e) => setTheme(e.detail)); + const themeSettings = settings[theme || 'strudelTheme']; + return { + theme, + setTheme, + settings: themeSettings, + isDark: !themeSettings.light, + isLight: !!themeSettings.light, + }; +} +// TODO: dedupe +function useEvent(name, onTrigger, useCapture = false) { + useEffect(() => { + document.addEventListener(name, onTrigger, useCapture); + return () => { + document.removeEventListener(name, onTrigger, useCapture); + }; + }, [onTrigger]); +} + +export default useTheme;