mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-23 11:38:37 +00:00
dynamic highlight color
+ refactor hooks
This commit is contained in:
parent
14cb954213
commit
3579b6f8f3
@ -49,11 +49,12 @@ const highlightField = StateField.define({
|
|||||||
try {
|
try {
|
||||||
for (let e of tr.effects) {
|
for (let e of tr.effects) {
|
||||||
if (e.is(setHighlights)) {
|
if (e.is(setHighlights)) {
|
||||||
|
const { haps } = e.value;
|
||||||
const marks =
|
const marks =
|
||||||
e.value
|
haps
|
||||||
.map((hap) =>
|
.map((hap) =>
|
||||||
(hap.context.locations || []).map(({ start, end }) => {
|
(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 from = tr.newDoc.line(start.line).from + start.column;
|
||||||
let to = tr.newDoc.line(end.line).from + end.column;
|
let to = tr.newDoc.line(end.line).from + end.column;
|
||||||
const l = tr.newDoc.length;
|
const l = tr.newDoc.length;
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { Icon } from './Icon';
|
|||||||
import styles from './MiniRepl.module.css';
|
import styles from './MiniRepl.module.css';
|
||||||
import './style.css';
|
import './style.css';
|
||||||
import { logger } from '@strudel.cycles/core';
|
import { logger } from '@strudel.cycles/core';
|
||||||
|
import useEvent from '../hooks/useEvent.mjs';
|
||||||
|
|
||||||
const getTime = () => getAudioContext().currentTime;
|
const getTime = () => getAudioContext().currentTime;
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ export function MiniRepl({
|
|||||||
punchcard,
|
punchcard,
|
||||||
canvasHeight = 200,
|
canvasHeight = 200,
|
||||||
theme,
|
theme,
|
||||||
|
highlightColor,
|
||||||
}) {
|
}) {
|
||||||
drawTime = drawTime || (punchcard ? [0, 4] : undefined);
|
drawTime = drawTime || (punchcard ? [0, 4] : undefined);
|
||||||
const evalOnMount = !!drawTime;
|
const evalOnMount = !!drawTime;
|
||||||
@ -69,6 +71,7 @@ export function MiniRepl({
|
|||||||
pattern,
|
pattern,
|
||||||
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
||||||
getTime: () => scheduler.now(),
|
getTime: () => scheduler.now(),
|
||||||
|
color: highlightColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
// set active pattern on ctrl+enter
|
// set active pattern on ctrl+enter
|
||||||
@ -148,13 +151,3 @@ export function MiniRepl({
|
|||||||
function useLogger(onTrigger) {
|
function useLogger(onTrigger) {
|
||||||
useEvent(logger.key, 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]);
|
|
||||||
}
|
|
||||||
|
|||||||
12
packages/react/src/hooks/useEvent.mjs
Normal file
12
packages/react/src/hooks/useEvent.mjs
Normal file
@ -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;
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { setHighlights } from '../components/CodeMirror6';
|
import { setHighlights } from '../components/CodeMirror6';
|
||||||
|
|
||||||
function useHighlighting({ view, pattern, active, getTime }) {
|
function useHighlighting({ view, pattern, active, getTime, color }) {
|
||||||
const highlights = useRef([]);
|
const highlights = useRef([]);
|
||||||
const lastEnd = useRef(0);
|
const lastEnd = useRef(0);
|
||||||
useEffect(() => {
|
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
|
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());
|
const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset());
|
||||||
highlights.current = highlights.current.concat(haps); // add potential new onsets
|
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) {
|
} catch (err) {
|
||||||
view.dispatch({ effects: setHighlights.of([]) });
|
view.dispatch({ effects: setHighlights.of({ haps: [] }) });
|
||||||
}
|
}
|
||||||
frame = requestAnimationFrame(updateHighlights);
|
frame = requestAnimationFrame(updateHighlights);
|
||||||
});
|
});
|
||||||
@ -30,10 +30,10 @@ function useHighlighting({ view, pattern, active, getTime }) {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
highlights.current = [];
|
highlights.current = [];
|
||||||
view.dispatch({ effects: setHighlights.of([]) });
|
view.dispatch({ effects: setHighlights.of({ haps: [] }) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [pattern, active, view]);
|
}, [pattern, active, view, color]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useHighlighting;
|
export default useHighlighting;
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
// import 'tailwindcss/tailwind.css';
|
// import 'tailwindcss/tailwind.css';
|
||||||
|
|
||||||
export { default as CodeMirror, flash } from './components/CodeMirror6';
|
export { default as CodeMirror, flash } from './components/CodeMirror6'; // !SSR
|
||||||
export * from './components/MiniRepl';
|
export * from './components/MiniRepl'; // !SSR
|
||||||
export { default as useHighlighting } from './hooks/useHighlighting';
|
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 usePostMessage } from './hooks/usePostMessage';
|
||||||
export { default as useStrudel } from './hooks/useStrudel';
|
|
||||||
export { default as useKeydown } from './hooks/useKeydown';
|
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 strudelTheme } from './themes/strudel-theme';
|
||||||
export { default as cx } from './cx';
|
export { default as cx } from './cx';
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { evalScope, controls } from '@strudel.cycles/core';
|
|||||||
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { prebake } from '../repl/prebake';
|
import { prebake } from '../repl/prebake';
|
||||||
import { themes } from '../repl/themes.mjs';
|
import { themes, settings } from '../repl/themes.mjs';
|
||||||
import './MiniRepl.css';
|
import './MiniRepl.css';
|
||||||
|
|
||||||
const theme = localStorage.getItem('strudel-theme') || 'strudelTheme';
|
const theme = localStorage.getItem('strudel-theme') || 'strudelTheme';
|
||||||
@ -36,6 +36,7 @@ export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
|
|||||||
.then(([res]) => setRepl(() => res.MiniRepl))
|
.then(([res]) => setRepl(() => res.MiniRepl))
|
||||||
.catch((err) => console.error(err));
|
.catch((err) => console.error(err));
|
||||||
}, []);
|
}, []);
|
||||||
|
// const { settings } = useTheme();
|
||||||
return Repl ? (
|
return Repl ? (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<Repl
|
<Repl
|
||||||
@ -45,6 +46,7 @@ export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
|
|||||||
punchcard={punchcard}
|
punchcard={punchcard}
|
||||||
canvasHeight={canvasHeight}
|
canvasHeight={canvasHeight}
|
||||||
theme={themes[theme]}
|
theme={themes[theme]}
|
||||||
|
highlightColor={settings[theme]?.foreground}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import XMarkIcon from '@heroicons/react/20/solid/XMarkIcon';
|
import XMarkIcon from '@heroicons/react/20/solid/XMarkIcon';
|
||||||
import { logger } from '@strudel.cycles/core';
|
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 { nanoid } from 'nanoid';
|
||||||
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { useEvent, loadedSamples } from './Repl';
|
import { loadedSamples } from './Repl';
|
||||||
import { Reference } from './Reference';
|
import { Reference } from './Reference';
|
||||||
import { themes, themeColors } from './themes.mjs';
|
import { themes, themeColors } from './themes.mjs';
|
||||||
|
|
||||||
@ -172,7 +173,7 @@ export function Footer({ context }) {
|
|||||||
{Object.entries(themes).map(([k, t]) => (
|
{Object.entries(themes).map(([k, t]) => (
|
||||||
<div
|
<div
|
||||||
key={k}
|
key={k}
|
||||||
className={classNames(
|
className={cx(
|
||||||
'border-2 border-transparent cursor-pointer p-4 bg-background bg-opacity-25 rounded-md',
|
'border-2 border-transparent cursor-pointer p-4 bg-background bg-opacity-25 rounded-md',
|
||||||
theme === k ? '!border-foreground' : '',
|
theme === k ? '!border-foreground' : '',
|
||||||
)}
|
)}
|
||||||
@ -225,24 +226,3 @@ function linkify(inputText) {
|
|||||||
|
|
||||||
return replacedText;
|
return replacedText;
|
||||||
}
|
}
|
||||||
|
|
||||||
function classNames(...classes) {
|
|
||||||
return classes.filter(Boolean).join(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* export function useTheme() {
|
|
||||||
const [theme, setTheme] = useState(localStorage.getItem('strudel-theme'));
|
|
||||||
useEvent('strudel-theme', (e) => {
|
|
||||||
console.log(e.detail);
|
|
||||||
setTheme(e.detail);
|
|
||||||
});
|
|
||||||
const themeSettings = settings[theme || 'strudelTheme'];
|
|
||||||
return {
|
|
||||||
theme,
|
|
||||||
setTheme,
|
|
||||||
settings: themeSettings,
|
|
||||||
isDark: !themeSettings.light,
|
|
||||||
isLight: !!themeSettings.light,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|||||||
@ -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 { 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 {
|
import {
|
||||||
getAudioContext,
|
getAudioContext,
|
||||||
getLoadedSamples,
|
getLoadedSamples,
|
||||||
@ -23,6 +23,7 @@ import { prebake } from './prebake.mjs';
|
|||||||
import * as tunes from './tunes.mjs';
|
import * as tunes from './tunes.mjs';
|
||||||
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
||||||
import { themes } from './themes.mjs';
|
import { themes } from './themes.mjs';
|
||||||
|
import useTheme from '../useTheme';
|
||||||
|
|
||||||
const initialTheme = localStorage.getItem('strudel-theme') || 'strudelTheme';
|
const initialTheme = localStorage.getItem('strudel-theme') || 'strudelTheme';
|
||||||
|
|
||||||
@ -171,12 +172,15 @@ export function Repl({ embedded = false }) {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { settings } = useTheme();
|
||||||
|
|
||||||
// highlighting
|
// highlighting
|
||||||
useHighlighting({
|
useHighlighting({
|
||||||
view,
|
view,
|
||||||
pattern,
|
pattern,
|
||||||
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
||||||
getTime: () => scheduler.now(),
|
getTime: () => scheduler.now(),
|
||||||
|
color: settings?.foreground,
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -299,15 +303,3 @@ export function Repl({ embedded = false }) {
|
|||||||
</ReplContext.Provider>
|
</ReplContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|||||||
27
website/src/useTheme.jsx
Normal file
27
website/src/useTheme.jsx
Normal file
@ -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;
|
||||||
Loading…
x
Reference in New Issue
Block a user