Merge pull request #431 from tidalcycles/themes

Themes
This commit is contained in:
Felix Roos 2023-02-10 23:14:47 +01:00 committed by GitHub
commit a253c26bee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 953 additions and 112 deletions

View File

@ -15,4 +15,6 @@ vite.config.js
!**/*.mjs !**/*.mjs
**/*.tsx **/*.tsx
**/*.ts **/*.ts
**/*.json **/*.json
**/dev-dist
**/dist

View File

@ -8,4 +8,5 @@ packages/mini/krill-parser.js
packages/xen/tunejs.js packages/xen/tunejs.js
paper paper
pnpm-lock.yaml pnpm-lock.yaml
pnpm-workspace.yaml pnpm-workspace.yaml
**/dev-dist

View File

@ -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;
@ -79,9 +80,17 @@ const highlightField = StateField.define({
provide: (f) => EditorView.decorations.from(f), provide: (f) => EditorView.decorations.from(f),
}); });
const extensions = [javascript(), strudelTheme, highlightField, flashField]; const extensions = [javascript(), highlightField, flashField];
export default function CodeMirror({ value, onChange, onViewChanged, onSelectionChange, options, editorDidMount }) { export default function CodeMirror({
value,
onChange,
onViewChanged,
onSelectionChange,
theme,
options,
editorDidMount,
}) {
const handleOnChange = useCallback( const handleOnChange = useCallback(
(value) => { (value) => {
onChange?.(value); onChange?.(value);
@ -106,6 +115,7 @@ export default function CodeMirror({ value, onChange, onViewChanged, onSelection
<> <>
<_CodeMirror <_CodeMirror
value={value} value={value}
theme={theme || strudelTheme}
onChange={handleOnChange} onChange={handleOnChange}
onCreateEditor={handleOnCreateEditor} onCreateEditor={handleOnCreateEditor}
onUpdate={handleOnUpdate} onUpdate={handleOnUpdate}

View File

@ -10,10 +10,21 @@ 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';
import useKeydown from '../hooks/useKeydown.mjs';
const getTime = () => getAudioContext().currentTime; const getTime = () => getAudioContext().currentTime;
export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTime, punchcard, canvasHeight = 200 }) { export function MiniRepl({
tune,
hideOutsideView = false,
enableKeyboard,
drawTime,
punchcard,
canvasHeight = 200,
theme,
highlightColor,
}) {
drawTime = drawTime || (punchcard ? [0, 4] : undefined); drawTime = drawTime || (punchcard ? [0, 4] : undefined);
const evalOnMount = !!drawTime; const evalOnMount = !!drawTime;
const drawContext = useCallback( const drawContext = useCallback(
@ -61,6 +72,7 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTi
pattern, pattern,
active: started && !activeCode?.includes('strudel disable-highlighting'), active: started && !activeCode?.includes('strudel disable-highlighting'),
getTime: () => scheduler.now(), getTime: () => scheduler.now(),
color: highlightColor,
}); });
// keyboard shortcuts // keyboard shortcuts
@ -132,7 +144,7 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTi
{error && <div className={styles.error}>{error.message}</div>} {error && <div className={styles.error}>{error.message}</div>}
</div> </div>
<div className={styles.body}> <div className={styles.body}>
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} />} {show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} theme={theme} />}
</div> </div>
{drawTime && ( {drawTime && (
<canvas <canvas
@ -161,18 +173,3 @@ export function MiniRepl({ tune, hideOutsideView = false, enableKeyboard, drawTi
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]);
}
// TODO: dedupe
function useKeydown(onTrigger) {
useEvent('keydown', onTrigger, true);
}

View File

@ -1,9 +1,9 @@
.container { .container {
@apply rounded-md overflow-hidden bg-[#222222]; @apply overflow-hidden;
} }
.header { .header {
@apply flex justify-between bg-slate-700 border-t border-slate-500; @apply flex justify-between bg-lineHighlight border-t border-l border-r border-lineHighlight rounded-t-md overflow-hidden;
} }
.buttons { .buttons {
@ -11,11 +11,11 @@
} }
.button { .button {
@apply cursor-pointer w-16 flex items-center justify-center p-1 bg-slate-700 border-r border-slate-500 text-white hover:bg-slate-600; @apply cursor-pointer w-16 flex items-center justify-center p-1 border-r border-lineHighlight text-foreground hover:bg-background;
} }
.buttonDisabled { .buttonDisabled {
@apply w-16 flex items-center justify-center p-1 bg-slate-600 text-slate-400 cursor-not-allowed; @apply w-16 flex items-center justify-center p-1 opacity-50 cursor-not-allowed border-r border-lineHighlight;
} }
.error { .error {

View File

@ -5,10 +5,10 @@
font-size: 18px; font-size: 18px;
} }
.cm-theme-light { .cm-theme {
width: 100%; width: 100%;
} }
.cm-line > * { .cm-theme-light {
background: #00000095; width: 100%;
} }

View 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;

View File

@ -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;

View File

@ -1,9 +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 cx } from './cx'; export { default as cx } from './cx';

235
pnpm-lock.yaml generated
View File

@ -373,6 +373,7 @@ importers:
'@types/node': ^18.0.0 '@types/node': ^18.0.0
'@types/react': ^18.0.26 '@types/react': ^18.0.26
'@types/react-dom': ^18.0.9 '@types/react-dom': ^18.0.9
'@uiw/codemirror-themes-all': ^4.19.8
'@vite-pwa/astro': ^0.0.1 '@vite-pwa/astro': ^0.0.1
astro: ^1.7.2 astro: ^1.7.2
canvas: ^2.11.0 canvas: ^2.11.0
@ -415,6 +416,7 @@ importers:
'@types/node': 18.11.18 '@types/node': 18.11.18
'@types/react': 18.0.27 '@types/react': 18.0.27
'@types/react-dom': 18.0.10 '@types/react-dom': 18.0.10
'@uiw/codemirror-themes-all': 4.19.8
astro: 1.9.2_@types+node@18.11.18 astro: 1.9.2_@types+node@18.11.18
canvas: 2.11.0 canvas: 2.11.0
fraction.js: 4.2.0 fraction.js: 4.2.0
@ -3994,6 +3996,173 @@ packages:
'@codemirror/view': 6.7.3 '@codemirror/view': 6.7.3
dev: false dev: false
/@uiw/codemirror-theme-abcdef/4.19.8:
resolution: {integrity: sha512-sR7srfMJRGTsAFQYEs+8IogUxUHDsMqNqZ4waIA6rNIvOH9Q1NYFEqTpqf4AeivkNeXm3N1LI+1nQAKhWzWpOA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-androidstudio/4.19.8:
resolution: {integrity: sha512-xhvEPZ1NvihIJVx7MA1Z8A8NwaTbM6mtBsfwM3mKAmzW6K6JcIJftcKe5z5wSPZk4J3S66vq8RYEMOBygSOdEQ==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-atomone/4.19.8:
resolution: {integrity: sha512-enZR8RPiKohyuEU4DOWA7A/xLs/+NsuvZAuGmg5PU43CQITzK1Wqri43/WxkGGsEIztejX439s1QuYxUwCCeUg==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-aura/4.19.8:
resolution: {integrity: sha512-DLoi2XEPNBD6Cxsp3v/nYw7jJ7sDEXET9+C43KZnk1dQ9H5e0HSpZhOkw1xmaCGzoXn9q7HInPa7GgUEMv8NPA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-bbedit/4.19.8:
resolution: {integrity: sha512-1vaVaVhMP2IB7H77W9yvPRhVr+8HGBWPDfFEVWAc7tfeU0xC2MFby05gO/7T6K2JWyj2lXnVw/i+oFNeDdcekQ==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-bespin/4.19.8:
resolution: {integrity: sha512-JyzmgCloEWhduH5GVYzHQ3vPc3zC0YVRiu7lOjgdOVRhr49M6KmwTytmbmqXbaqmSZKFUJqiHjz8VswqcNC4dw==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-darcula/4.19.8:
resolution: {integrity: sha512-fPKyzMqCtBfez2ubC/77AP0BfE+XOio+sy8oRYfX3HqRQEivnnEddQJnTZVz0kKSLYSryQHbOMw/qwgInPvBCA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-dracula/4.19.8:
resolution: {integrity: sha512-YHQJUDnYKrew/YkEPQG3SQFcnKr6MzVbIXxmdKuxlCTrySNFNMK19dKrzY3fwc+Lgpp5r26QLLocTZyCvu1xeA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-duotone/4.19.8:
resolution: {integrity: sha512-SJkjyKxU2Nazv6uEctvRdz62/ZQ8rBP4xtXrjF/zcjOdWmx2ecUAGn/j5Euw5V55Dr74yIQFs0L+GB5UTHCHDA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-eclipse/4.19.8:
resolution: {integrity: sha512-6XXzYFC809e5MWT/NNTqoZ0N9H6P8mXwIEGDfigh33VU1TJPPpO1LCWdf59VcXx/xIY0ctDp7rU7hTy3f3tHkw==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-github/4.19.8:
resolution: {integrity: sha512-RUAzHGkG2G2PXTxsH4KbKVCKqPs/+H6xvhCT3Q4YiPj1W2aGSfPwZZ8cLLCm9dYaIh/+H6L+AcoiUtvADjyKsQ==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-gruvbox-dark/4.19.8:
resolution: {integrity: sha512-EQepnqhfcu7HY2uutigzflZ9OFzDcF9eq4xF53UylD+WZBdwq2qarZMQgBfOXZZKYI3XcTny064fa7Xxklhs1g==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-material/4.19.8:
resolution: {integrity: sha512-dm7FtZkJSZ4AV+9vXxKUDnXLTHaqWnsmn9HOb6AmLiHmHcLFLDgeosSBnA80fwXBhd6QswJI0cEnKHAvcC/vFg==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-noctis-lilac/4.19.8:
resolution: {integrity: sha512-vPH8KOiRgcBK1V7/BN04n8K03cfR4CEmOGZUMKpcLlSZwWRe0+JPk6S2qtjbJ072Fsn1ZrcLRYrqtaAJQNOVnw==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-nord/4.19.8:
resolution: {integrity: sha512-qBqKJI9fa35rFefXqaiw3FCXWSpD+65+Weji3g/AgB0m0+MHG+3rkC+rLZ4PE2QpkcMz6wHj89EMwOW9W1i3cA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-okaidia/4.19.8:
resolution: {integrity: sha512-gxEThgbbfIxKjk7UzD0D8gw5bVpgxDct3e6r9/WegZw7tTvTHMnz9wutGbJp2cYSwdgLHoQJZzZBi6jKhExZSg==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-solarized/4.19.8:
resolution: {integrity: sha512-xHp2i6zWBMw2+kZX0GUYi7pCL3ZL1qWSO8OaleHCkpY3jn+lfLSJOAYYkajvNyu3tPsiKQuAgpCQ6AYFWOr6BQ==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-sublime/4.19.8:
resolution: {integrity: sha512-tapkCty8lwpRw2CAY5bPiK8S2ptdTL4d1X/IkCVyHqbC0B8728iQJu3E4Rrka7yggFx4QGUpyZB9UTIFYURtsw==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-tokyo-night-day/4.19.8:
resolution: {integrity: sha512-HDKp2KDjluubpe+7M31SohWUFCW7wGQIvoD60xr86OkPZDdqsCVUFmna6rqfttV6S2OsgVNJPBzZvOF398Eapg==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-tokyo-night-storm/4.19.8:
resolution: {integrity: sha512-Kp8HIf1H+pgbRx/ZhRsqqN5qfyx3SVVfjnUPGltsdBymqh8QGdr7LC8Tp0P5+Yjb6CqF6Dgx7RnQDj1XZ94rbA==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-tokyo-night/4.19.8:
resolution: {integrity: sha512-LrfB+EgZqBNlMF6Zqk8RrlsfVzKe6Ple7ckvREV6kSuwxncF5FlhzVL3cWgxNfNQ7TJ5ySVSvw4fWOcxIcc1LQ==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-vscode/4.19.8:
resolution: {integrity: sha512-UdUuJfNjG2y88taP5gsEA9AQlhRTURnl+9kl+LOJkQtXavrqp3J8gUAHRhEWxN7k8q8YimsSVcQ4yw0FKAgXQQ==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-theme-xcode/4.19.8:
resolution: {integrity: sha512-VYCRhStYujUNGIkiaZZ1o5OO2Z2edsKkYCCYSy0rwb4G17vT6sd4YDnmJQuF0gzqQkTIdDxSb6aCM9ISkPABlg==}
dependencies:
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-themes-all/4.19.8:
resolution: {integrity: sha512-0kAWabe46Vkz+gwNfaXvn/0adeUSCTAuZC0d7Nu8ebcr1TjT1fDLFNZIXlwidn40suiXq8FDfZ9euArx3RPg0Q==}
dependencies:
'@uiw/codemirror-theme-abcdef': 4.19.8
'@uiw/codemirror-theme-androidstudio': 4.19.8
'@uiw/codemirror-theme-atomone': 4.19.8
'@uiw/codemirror-theme-aura': 4.19.8
'@uiw/codemirror-theme-bbedit': 4.19.8
'@uiw/codemirror-theme-bespin': 4.19.8
'@uiw/codemirror-theme-darcula': 4.19.8
'@uiw/codemirror-theme-dracula': 4.19.8
'@uiw/codemirror-theme-duotone': 4.19.8
'@uiw/codemirror-theme-eclipse': 4.19.8
'@uiw/codemirror-theme-github': 4.19.8
'@uiw/codemirror-theme-gruvbox-dark': 4.19.8
'@uiw/codemirror-theme-material': 4.19.8
'@uiw/codemirror-theme-noctis-lilac': 4.19.8
'@uiw/codemirror-theme-nord': 4.19.8
'@uiw/codemirror-theme-okaidia': 4.19.8
'@uiw/codemirror-theme-solarized': 4.19.8
'@uiw/codemirror-theme-sublime': 4.19.8
'@uiw/codemirror-theme-tokyo-night': 4.19.8
'@uiw/codemirror-theme-tokyo-night-day': 4.19.8
'@uiw/codemirror-theme-tokyo-night-storm': 4.19.8
'@uiw/codemirror-theme-vscode': 4.19.8
'@uiw/codemirror-theme-xcode': 4.19.8
'@uiw/codemirror-themes': 4.19.8
dev: false
/@uiw/codemirror-themes/4.19.7_a4vbhepr4qhxm5cldqd4jpyase: /@uiw/codemirror-themes/4.19.7_a4vbhepr4qhxm5cldqd4jpyase:
resolution: {integrity: sha512-M/42RkPI60ItlssmNuEoZO2MQvlY6fRmdX7XRUAhKjxczZoaq8xS6HIvv1whGf2zGsTrwdVTPCm6ls0l17dvPA==} resolution: {integrity: sha512-M/42RkPI60ItlssmNuEoZO2MQvlY6fRmdX7XRUAhKjxczZoaq8xS6HIvv1whGf2zGsTrwdVTPCm6ls0l17dvPA==}
peerDependencies: peerDependencies:
@ -4005,6 +4174,14 @@ packages:
'@codemirror/view': 6.7.3 '@codemirror/view': 6.7.3
dev: false dev: false
/@uiw/codemirror-themes/4.19.8:
resolution: {integrity: sha512-k+0molX6YuQZ7vYymS5DEl3NcHxVE4VP4J0nB7RBnzLrhDwT2K8R5ie7J2eH+4bpppIK1ZZdZ6Mie7U48dH5dQ==}
dependencies:
'@codemirror/language': 6.4.0
'@codemirror/state': 6.2.0
'@codemirror/view': 6.7.3
dev: false
/@uiw/react-codemirror/4.19.7_b6o5qp6ml4k7skggixohr5abde: /@uiw/react-codemirror/4.19.7_b6o5qp6ml4k7skggixohr5abde:
resolution: {integrity: sha512-IHvpYWVSdiaHX0Fk6oY6YyAJigDnyvSpWKNUTRzsMNxB+8/wqZ8lior4TprXH0zyLxW5F1+bTyifFFTeg+X3Sw==} resolution: {integrity: sha512-IHvpYWVSdiaHX0Fk6oY6YyAJigDnyvSpWKNUTRzsMNxB+8/wqZ8lior4TprXH0zyLxW5F1+bTyifFFTeg+X3Sw==}
peerDependencies: peerDependencies:
@ -10221,6 +10398,17 @@ packages:
- supports-color - supports-color
dev: true dev: true
/postcss-import/14.1.0:
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
engines: {node: '>=10.0.0'}
peerDependencies:
postcss: ^8.0.0
dependencies:
postcss-value-parser: 4.2.0
read-cache: 1.0.0
resolve: 1.22.1
dev: false
/postcss-import/14.1.0_postcss@8.4.21: /postcss-import/14.1.0_postcss@8.4.21:
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
@ -10231,6 +10419,16 @@ packages:
postcss-value-parser: 4.2.0 postcss-value-parser: 4.2.0
read-cache: 1.0.0 read-cache: 1.0.0
resolve: 1.22.1 resolve: 1.22.1
dev: true
/postcss-js/4.0.0:
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
engines: {node: ^12 || ^14 || >= 16}
peerDependencies:
postcss: ^8.3.3
dependencies:
camelcase-css: 2.0.1
dev: false
/postcss-js/4.0.0_postcss@8.4.21: /postcss-js/4.0.0_postcss@8.4.21:
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==} resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
@ -10240,6 +10438,23 @@ packages:
dependencies: dependencies:
camelcase-css: 2.0.1 camelcase-css: 2.0.1
postcss: 8.4.21 postcss: 8.4.21
dev: true
/postcss-load-config/3.1.4:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
engines: {node: '>= 10'}
peerDependencies:
postcss: '>=8.0.9'
ts-node: '>=9.0.0'
peerDependenciesMeta:
postcss:
optional: true
ts-node:
optional: true
dependencies:
lilconfig: 2.0.6
yaml: 1.10.2
dev: false
/postcss-load-config/3.1.4_postcss@8.4.21: /postcss-load-config/3.1.4_postcss@8.4.21:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
@ -10257,6 +10472,15 @@ packages:
postcss: 8.4.21 postcss: 8.4.21
yaml: 1.10.2 yaml: 1.10.2
/postcss-nested/6.0.0:
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
engines: {node: '>=12.0'}
peerDependencies:
postcss: ^8.2.14
dependencies:
postcss-selector-parser: 6.0.11
dev: false
/postcss-nested/6.0.0_postcss@8.4.21: /postcss-nested/6.0.0_postcss@8.4.21:
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
engines: {node: '>=12.0'} engines: {node: '>=12.0'}
@ -10265,6 +10489,7 @@ packages:
dependencies: dependencies:
postcss: 8.4.21 postcss: 8.4.21
postcss-selector-parser: 6.0.11 postcss-selector-parser: 6.0.11
dev: true
/postcss-selector-parser/6.0.10: /postcss-selector-parser/6.0.10:
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
@ -11873,6 +12098,8 @@ packages:
resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==} resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==}
engines: {node: '>=12.13.0'} engines: {node: '>=12.13.0'}
hasBin: true hasBin: true
peerDependencies:
postcss: ^8.0.9
dependencies: dependencies:
arg: 5.0.2 arg: 5.0.2
chokidar: 3.5.3 chokidar: 3.5.3
@ -11889,10 +12116,10 @@ packages:
object-hash: 3.0.0 object-hash: 3.0.0
picocolors: 1.0.0 picocolors: 1.0.0
postcss: 8.4.21 postcss: 8.4.21
postcss-import: 14.1.0_postcss@8.4.21 postcss-import: 14.1.0
postcss-js: 4.0.0_postcss@8.4.21 postcss-js: 4.0.0
postcss-load-config: 3.1.4_postcss@8.4.21 postcss-load-config: 3.1.4
postcss-nested: 6.0.0_postcss@8.4.21 postcss-nested: 6.0.0
postcss-selector-parser: 6.0.11 postcss-selector-parser: 6.0.11
postcss-value-parser: 4.2.0 postcss-value-parser: 4.2.0
quick-lru: 5.1.1 quick-lru: 5.1.1

View File

@ -38,6 +38,7 @@
"@types/node": "^18.0.0", "@types/node": "^18.0.0",
"@types/react": "^18.0.26", "@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9", "@types/react-dom": "^18.0.9",
"@uiw/codemirror-themes-all": "^4.19.8",
"astro": "^1.7.2", "astro": "^1.7.2",
"canvas": "^2.11.0", "canvas": "^2.11.0",
"fraction.js": "^4.2.0", "fraction.js": "^4.2.0",

View File

@ -1,9 +1,12 @@
--- ---
import { pwaInfo } from 'virtual:pwa-info'; import { pwaInfo } from 'virtual:pwa-info';
import '../styles/index.css'; import '../styles/index.css';
import { settings } from '../repl/themes.mjs';
const { BASE_URL } = import.meta.env; const { BASE_URL } = import.meta.env;
const base = BASE_URL; const base = BASE_URL;
const { strudelTheme } = settings;
--- ---
<!-- Global Metadata --> <!-- Global Metadata -->
@ -27,4 +30,55 @@ const base = BASE_URL;
<script src="./make-scrollable-code-focusable.js" is:inline></script> <script src="./make-scrollable-code-focusable.js" is:inline></script>
<script src="/src/pwa.ts"></script> <script src="/src/pwa.ts"></script>
<!-- this does not work for some reason: -->
<!-- <style is:global define:vars={strudelTheme}></style> -->
<!-- the following variables are just a fallback to make sure everything is readable without JS -->
<style is:global>
:root {
--background: #222;
--lineBackground: #22222250;
--foreground: #fff;
--caret: #ffcc00;
--selection: rgba(128, 203, 196, 0.5);
--selectionMatch: #036dd626;
--lineHighlight: #00000050;
--gutterBackground: transparent;
--gutterForeground: #8a919966;
}
</style>
{pwaInfo && <Fragment set:html={pwaInfo.webManifest.linkTag} />} {pwaInfo && <Fragment set:html={pwaInfo.webManifest.linkTag} />}
<script define:vars={{ settings, strudelTheme }} is:inline>
const themeStyle = document.createElement('style');
themeStyle.id = 'strudel-theme';
document.head.append(themeStyle);
function getTheme(name) {
if (!settings[name]) {
console.warn('theme', name, 'has no settings');
}
return {
name,
settings: settings[name] || settings.strudelTheme,
};
}
function setTheme(name) {
const { settings } = getTheme(name);
// set css variables
themeStyle.innerHTML = `:root {
${Object.entries(settings)
// important to override fallback
.map(([key, value]) => `--${key}: ${value} !important;`)
.join('\n')}
}`;
// tailwind dark mode
if (settings.light) {
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
}
// persist theme name
localStorage.setItem('strudel-theme', name || 'strudelTheme');
}
setTheme(localStorage.getItem('strudel-theme'));
document.addEventListener('strudel-theme', (e) => setTheme(e.detail));
</script>

View File

@ -19,13 +19,13 @@ const langCode = 'en'; // getLanguageFromURL(currentPage);
const sidebar = SIDEBAR[langCode]; const sidebar = SIDEBAR[langCode];
--- ---
<nav class="flex justify-between py-2 px-4 items-center h-14 max-h-14 bg-[#161616]" title="Top Navigation"> <nav class="flex justify-between py-2 px-4 items-center h-14 max-h-14 bg-lineHighlight text-foreground" title="Top Navigation">
<!-- <div class="menu-toggle"> <!-- <div class="menu-toggle">
<SidebarToggle client:idle /> <SidebarToggle client:idle />
</div> --> </div> -->
<div class="flex overflow-visible items-center grow" style="overflow:visible"> <div class="flex overflow-visible items-center grow" style="overflow:visible">
<a href="/" class="flex items-center text-2xl space-x-2"> <a href="/" class="flex items-center text-2xl space-x-2">
<h1 class="text-white font-bold flex space-x-2 items-baseline text-xl"> <h1 class="font-bold flex space-x-2 items-baseline text-xl">
<span>🌀</span> <span>🌀</span>
<div class="flex space-x-1 items-baseline"> <div class="flex space-x-1 items-baseline">
<span class="">strudel</span> <span class="">strudel</span>

View File

@ -14,7 +14,7 @@ const langCode = 'en'; // getLanguageFromURL(currentPage);
const sidebar = SIDEBAR[langCode]; const sidebar = SIDEBAR[langCode];
--- ---
<nav aria-labelledby="grid-left" class="max-h-full overflow-auto pb-20"> <nav aria-labelledby="grid-left" class="max-h-full overflow-auto pb-20 text-foreground">
<ul> <ul>
{ {
Object.entries(sidebar).map(([header, children]) => ( Object.entries(sidebar).map(([header, children]) => (
@ -27,8 +27,8 @@ const sidebar = SIDEBAR[langCode];
return ( return (
<li class=""> <li class="">
<a <a
class={`pl-4 py-0.5 w-full hover:bg-header block${ class={`pl-4 py-0.5 w-full hover:bg-lineHighlight block${
currentPageMatch === child.link ? ' bg-header' : '' currentPageMatch === child.link ? ' bg-lineHighlight' : ''
}`} }`}
href={url} href={url}
aria-current={currentPageMatch === child.link ? 'page' : false} aria-current={currentPageMatch === child.link ? 'page' : false}

View File

@ -22,7 +22,7 @@ const currentPage = Astro.url.pathname;
<span>On this Page:</span> <span>On this Page:</span>
<TableOfContents client:media="(max-width: 50em)" headings={headings} currentPage={currentPage} /> <TableOfContents client:media="(max-width: 50em)" headings={headings} currentPage={currentPage} />
</nav> --> </nav> -->
<div class="prose prose-invert max-w-full pb-8"> <div class="prose dark:prose-invert max-w-full pb-8">
<slot /> <slot />
</div> </div>
</section> </section>

View File

@ -12,7 +12,7 @@ const { headings, githubEditUrl } = Astro.props as Props;
const currentPage = Astro.url.pathname; const currentPage = Astro.url.pathname;
--- ---
<nav aria-labelledby="grid-right" class="w-64"> <nav aria-labelledby="grid-right" class="w-64 text-foreground">
<TableOfContents client:media="(min-width: 50em)" headings={headings} currentPage={currentPage} /> <TableOfContents client:media="(min-width: 50em)" headings={headings} currentPage={currentPage} />
<MoreMenu editHref={githubEditUrl} /> <MoreMenu editHref={githubEditUrl} />
</nav> </nav>

View File

@ -78,9 +78,9 @@ const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[]; curren
<a <a
href={`${currentPage}#${heading.slug}`} href={`${currentPage}#${heading.slug}`}
onClick={onLinkClick} onClick={onLinkClick}
className={`py-0.5 block cursor-pointer w-full border-l-4 border-header hover:bg-header ${ className={`py-0.5 block cursor-pointer w-full border-l-4 border-lineHighlight hover:bg-lineHighlight ${
['pl-4', 'pl-9', 'pl-12'][heading.depth - minDepth] ['pl-4', 'pl-9', 'pl-12'][heading.depth - minDepth]
} ${currentID === heading.slug ? 'bg-header' : ''}`.trim()} } ${currentID === heading.slug ? 'bg-lineHighlight' : ''}`.trim()}
> >
{unescape(heading.text)} {unescape(heading.text)}
</a> </a>

View File

@ -0,0 +1,9 @@
.cm-activeLine {
background-color: transparent !important;
}
.cm-theme {
background-color: var(--background);
border: 1px solid var(--lineHighlight);
padding: 2px;
}

View File

@ -2,6 +2,10 @@ 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, settings } from '../repl/themes.mjs';
import './MiniRepl.css';
const theme = localStorage.getItem('strudel-theme') || 'strudelTheme';
let modules; let modules;
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
@ -32,9 +36,18 @@ 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 tune={tune} hideOutsideView={true} drawTime={drawTime} punchcard={punchcard} canvasHeight={canvasHeight} /> <Repl
tune={tune}
hideOutsideView={true}
drawTime={drawTime}
punchcard={punchcard}
canvasHeight={canvasHeight}
theme={themes[theme]}
highlightColor={settings[theme]?.foreground}
/>
</div> </div>
) : ( ) : (
<pre>{tune}</pre> <pre>{tune}</pre>

View File

@ -23,7 +23,7 @@ export default function MobileNav({ sidebar }) {
<div className="space-y-1 px-4 py-4 bg-[#161616]"> <div className="space-y-1 px-4 py-4 bg-[#161616]">
<a <a
href=".." href=".."
className="py-2 flex cursor-pointer items-center space-x-1 hover:bg-bg hover:px-2 rounded-md" className="py-2 flex cursor-pointer items-center space-x-1 hover:bg-background hover:px-2 rounded-md"
> >
<span>go to REPL</span> <span>go to REPL</span>
</a> </a>
@ -36,7 +36,9 @@ export default function MobileNav({ sidebar }) {
as="a" as="a"
href={`/${item.link}`} href={`/${item.link}`}
className={classNames( className={classNames(
item.current ? 'bg-bg text-white' : 'text-gray-300 hover:bg-bg hover:text-white', item.current
? 'bg-background text-white'
: 'text-gray-300 hover:bg-lineHighlight hover:text-white',
'block px-3 py-2 rounded-md text-base font-medium', 'block px-3 py-2 rounded-md text-base font-medium',
)} )}
aria-current={item.current ? 'page' : undefined} aria-current={item.current ? 'page' : undefined}

View File

@ -22,7 +22,7 @@ const currentFile = `src/pages${currentPage.replace(/\/$/, '')}.mdx`;
const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`; const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
--- ---
<html dir={frontmatter.dir ?? 'ltr'} lang={frontmatter.lang ?? 'en'} class="initial"> <html dir={frontmatter.dir ?? 'ltr'} lang={frontmatter.lang ?? 'en'} class="initial dark">
<head> <head>
<HeadCommon /> <HeadCommon />
<HeadSEO frontmatter={frontmatter} canonicalUrl={canonicalURL} /> <HeadSEO frontmatter={frontmatter} canonicalUrl={canonicalURL} />
@ -31,7 +31,7 @@ const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
</title> </title>
</head> </head>
<body class="h-screen text-gray-50"> <body class="h-screen text-gray-50 bg-background">
<div class="w-full h-full space-y-4 flex flex-col"> <div class="w-full h-full space-y-4 flex flex-col">
<header class="max-w-full fixed top-0 w-full z-[100]"> <header class="max-w-full fixed top-0 w-full z-[100]">
<Header currentPage={currentPage} /> <Header currentPage={currentPage} />

View File

@ -3,12 +3,12 @@ import HeadCommon from '../components/HeadCommon.astro';
import { Repl } from '../repl/Repl.jsx'; import { Repl } from '../repl/Repl.jsx';
--- ---
<html lang="en"> <html lang="en" class="dark">
<head> <head>
<HeadCommon /> <HeadCommon />
<title>Strudel REPL</title> <title>Strudel REPL</title>
</head> </head>
<body> <body class="bg-background">
<Repl client:only="react" /> <Repl client:only="react" />
</body> </body>
</html> </html>

View File

@ -1,15 +1,17 @@
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, { useContext, useCallback, useLayoutEffect, useRef, useState } from 'react'; import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
import { useEvent, loadedSamples, ReplContext } from './Repl'; import { loadedSamples } from './Repl';
import { Reference } from './Reference'; import { Reference } from './Reference';
import { themes, themeColors } from './themes.mjs';
export function Footer({ context }) { export function Footer({ context }) {
// const [activeFooter, setActiveFooter] = useState('console'); // const [activeFooter, setActiveFooter] = useState('console');
// const { activeFooter, setActiveFooter, isZen } = useContext?.(ReplContext); // const { activeFooter, setActiveFooter, isZen } = useContext?.(ReplContext);
const { activeFooter, setActiveFooter, isZen } = context; const { activeFooter, setActiveFooter, isZen, theme, setTheme } = context;
const footerContent = useRef(); const footerContent = useRef();
const [log, setLog] = useState([]); const [log, setLog] = useState([]);
@ -54,8 +56,8 @@ export function Footer({ context }) {
<div <div
onClick={() => setActiveFooter(name)} onClick={() => setActiveFooter(name)}
className={cx( className={cx(
'h-8 px-2 text-white cursor-pointer hover:text-tertiary flex items-center space-x-1 border-b', 'h-8 px-2 text-foreground cursor-pointer hover:text-tertiary flex items-center space-x-1 border-b',
activeFooter === name ? 'border-white hover:border-tertiary' : 'border-transparent', activeFooter === name ? 'border-foreground hover:border-tertiary' : 'border-transparent',
)} )}
> >
{label || name} {label || name}
@ -67,16 +69,17 @@ export function Footer({ context }) {
return null; return null;
} }
return ( return (
<footer className="bg-footer z-[20]"> <footer className="bg-lineHighlight z-[20]">
<div className="flex justify-between px-2"> <div className="flex justify-between px-2">
<div className={cx('flex select-none', activeFooter && 'pb-2')}> <div className={cx('flex select-none max-w-full overflow-auto', activeFooter && 'pb-2')}>
<FooterTab name="intro" label="welcome" /> <FooterTab name="intro" label="welcome" />
<FooterTab name="samples" /> <FooterTab name="samples" />
<FooterTab name="console" /> <FooterTab name="console" />
<FooterTab name="reference" /> <FooterTab name="reference" />
<FooterTab name="theme" />
</div> </div>
{activeFooter !== '' && ( {activeFooter !== '' && (
<button onClick={() => setActiveFooter('')} className="text-white" aria-label="Close Panel"> <button onClick={() => setActiveFooter('')} className="text-foreground" aria-label="Close Panel">
<XMarkIcon className="w-5 h-5" /> <XMarkIcon className="w-5 h-5" />
</button> </button>
)} )}
@ -87,7 +90,7 @@ export function Footer({ context }) {
ref={footerContent} ref={footerContent}
> >
{activeFooter === 'intro' && ( {activeFooter === 'intro' && (
<div className="prose prose-invert max-w-[600px] pt-2 font-sans pb-8 px-4"> <div className="prose dark:prose-invert max-w-[600px] pt-2 font-sans pb-8 px-4">
<h3> <h3>
<span className={cx('animate-spin inline-block select-none')}>🌀</span> welcome <span className={cx('animate-spin inline-block select-none')}>🌀</span> welcome
</h3> </h3>
@ -132,7 +135,7 @@ export function Footer({ context }) {
</div> </div>
)} )}
{activeFooter === 'console' && ( {activeFooter === 'console' && (
<div className="break-all px-4"> <div className="break-all px-4 dark:text-white text-stone-900">
{log.map((l, i) => { {log.map((l, i) => {
const message = linkify(l.message); const message = linkify(l.message);
return ( return (
@ -148,8 +151,8 @@ export function Footer({ context }) {
</div> </div>
)} )}
{activeFooter === 'samples' && ( {activeFooter === 'samples' && (
<div className="break-normal w-full px-4"> <div className="break-normal w-full px-4 dark:text-white text-stone-900">
<span className="text-white">{loadedSamples.length} banks loaded:</span> <span>{loadedSamples.length} banks loaded:</span>
{loadedSamples.map(([name, samples]) => ( {loadedSamples.map(([name, samples]) => (
<span key={name} className="cursor-pointer hover:text-tertiary" onClick={() => {}}> <span key={name} className="cursor-pointer hover:text-tertiary" onClick={() => {}}>
{' '} {' '}
@ -165,6 +168,34 @@ export function Footer({ context }) {
</div> </div>
)} )}
{activeFooter === 'reference' && <Reference />} {activeFooter === 'reference' && <Reference />}
{activeFooter === 'theme' && (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-2 p-2">
{Object.entries(themes).map(([k, t]) => (
<div
key={k}
className={cx(
'border-2 border-transparent cursor-pointer p-4 bg-background bg-opacity-25 rounded-md',
theme === k ? '!border-foreground' : '',
)}
onClick={() => {
setTheme(k);
document.dispatchEvent(
new CustomEvent('strudel-theme', {
detail: k,
}),
);
}}
>
<div className="mb-2 w-full text-center text-foreground">{k}</div>
<div className="flex justify-stretch overflow-hidden rounded-md">
{themeColors(t).map((c, i) => (
<div key={i} className="grow h-6" style={{ background: c }} />
))}
</div>
</div>
))}
</div>
)}
</div> </div>
)} )}
</footer> </footer>

View File

@ -32,7 +32,7 @@ export function Header({ context }) {
id="header" id="header"
className={cx( className={cx(
'py-1 flex-none w-full text-black justify-between z-[100] text-lg select-none sticky top-0', 'py-1 flex-none w-full text-black justify-between z-[100] text-lg select-none sticky top-0',
!isZen && !isEmbedded && 'bg-header', !isZen && !isEmbedded && 'bg-lineHighlight',
isEmbedded ? 'flex' : 'md:flex', isEmbedded ? 'flex' : 'md:flex',
)} )}
> >
@ -48,8 +48,7 @@ export function Header({ context }) {
}} }}
className={cx( className={cx(
isEmbedded ? 'text-l cursor-pointer' : 'text-xl', isEmbedded ? 'text-l cursor-pointer' : 'text-xl',
// 'bg-clip-text bg-gradient-to-r from-primary to-secondary text-transparent font-bold', 'text-foreground font-bold flex space-x-2 items-center',
'text-white font-bold flex space-x-2 items-center',
)} )}
> >
<div <div
@ -66,7 +65,7 @@ export function Header({ context }) {
</h1> </h1>
</div> </div>
{!isZen && ( {!isZen && (
<div className="flex max-w-full overflow-auto text-white "> <div className="flex max-w-full overflow-auto text-foreground">
<button <button
onClick={handleTogglePlay} onClick={handleTogglePlay}
title={started ? 'stop' : 'play'} title={started ? 'stop' : 'play'}

View File

@ -5,16 +5,16 @@ const visibleFunctions = jsdocJson.docs
export function Reference() { export function Reference() {
return ( return (
<div className="flex h-full w-full pt-2"> <div className="flex h-full w-full pt-2 text-foreground">
<div className="w-64 flex-none h-full overflow-y-auto overflow-x-hidden pr-4"> <div className="w-64 flex-none h-full overflow-y-auto overflow-x-hidden pr-4">
{visibleFunctions.map((entry, i) => ( {visibleFunctions.map((entry, i) => (
<a key={i} className="cursor-pointer block hover:bg-linegray py-1 px-4" href={`#doc-${i}`}> <a key={i} className="cursor-pointer block hover:bg-lineHighlight py-1 px-4" href={`#doc-${i}`}>
{entry.name} {/* <span className="text-gray-600">{entry.meta.filename}</span> */} {entry.name} {/* <span className="text-gray-600">{entry.meta.filename}</span> */}
</a> </a>
))} ))}
</div> </div>
<div className="break-normal w-full h-full overflow-auto pl-4 flex relative"> <div className="break-normal w-full h-full overflow-auto pl-4 flex relative">
<div className="prose prose-invert"> <div className="prose dark:prose-invert">
<h2>API Reference</h2> <h2>API Reference</h2>
<p> <p>
This is the long list functions you can use! Remember that you don't need to remember all of those and that This is the long list functions you can use! Remember that you don't need to remember all of those and that

View File

@ -10,11 +10,11 @@
opacity: 0.5; opacity: 0.5;
} }
.cm-gutters {
display: none !important;
}
.cm-content { .cm-content {
padding-top: 12px !important; padding-top: 12px !important;
padding-left: 8px !important; padding-left: 8px !important;
} }
.cm-line > * {
background: var(--lineBackground);
}

View File

@ -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,
@ -22,6 +22,10 @@ import { Header } from './Header';
import { prebake } from './prebake.mjs'; 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 useTheme from '../useTheme';
const initialTheme = localStorage.getItem('strudel-theme') || 'strudelTheme';
initAudioOnFirstClick(); initAudioOnFirstClick();
@ -109,6 +113,7 @@ export const ReplContext = createContext(null);
export function Repl({ embedded = false }) { export function Repl({ embedded = false }) {
const isEmbedded = embedded || window.location !== window.parent.location; const isEmbedded = embedded || window.location !== window.parent.location;
const [view, setView] = useState(); // codemirror view const [view, setView] = useState(); // codemirror view
const [theme, setTheme] = useState(initialTheme);
const [lastShared, setLastShared] = useState(); const [lastShared, setLastShared] = useState();
const [activeFooter, setActiveFooter] = useState(''); const [activeFooter, setActiveFooter] = useState('');
const [isZen, setIsZen] = useState(false); const [isZen, setIsZen] = useState(false);
@ -167,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,
}); });
// //
@ -255,6 +263,8 @@ export function Repl({ embedded = false }) {
handleShare, handleShare,
isZen, isZen,
setIsZen, setIsZen,
theme,
setTheme,
}; };
return ( return (
// bg-gradient-to-t from-blue-900 to-slate-900 // bg-gradient-to-t from-blue-900 to-slate-900
@ -269,6 +279,7 @@ export function Repl({ embedded = false }) {
<Header context={context} /> <Header context={context} />
<section className="grow flex text-gray-100 relative overflow-auto cursor-text pb-0" id="code"> <section className="grow flex text-gray-100 relative overflow-auto cursor-text pb-0" id="code">
<CodeMirror <CodeMirror
theme={themes[theme] || themes.strudelTheme}
value={code} value={code}
onChange={handleChangeCode} onChange={handleChangeCode}
onViewChanged={setView} onViewChanged={setView}
@ -276,7 +287,7 @@ export function Repl({ embedded = false }) {
/> />
</section> </section>
{error && ( {error && (
<div className="text-red-500 p-4 bg-lineblack animate-pulse">{error.message || 'Unknown Error :-/'}</div> <div className="text-red-500 p-4 bg-lineHighlight animate-pulse">{error.message || 'Unknown Error :-/'}</div>
)} )}
{isEmbedded && !started && ( {isEmbedded && !started && (
<button <button
@ -292,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);
}

457
website/src/repl/themes.mjs Normal file
View File

@ -0,0 +1,457 @@
import {
abcdef,
androidstudio,
atomone,
aura,
bespin,
darcula,
dracula,
duotoneDark,
eclipse,
githubDark,
gruvboxDark,
materialDark,
nord,
okaidia,
solarizedDark,
sublime,
tokyoNight,
tokyoNightStorm,
vscodeDark,
xcodeDark,
bbedit,
duotoneLight,
githubLight,
gruvboxLight,
materialLight,
noctisLilac,
solarizedLight,
tokyoNightDay,
xcodeLight,
} from '@uiw/codemirror-themes-all';
import strudelTheme from '@strudel.cycles/react/src/themes/strudel-theme';
export const themes = {
strudelTheme,
abcdef,
androidstudio,
atomone,
aura,
bespin,
darcula,
dracula,
duotoneDark,
eclipse,
githubDark,
gruvboxDark,
materialDark,
nord,
okaidia,
solarizedDark,
sublime,
tokyoNight,
tokyoNightStorm,
vscodeDark,
xcodeDark,
bbedit,
duotoneLight,
githubLight,
gruvboxLight,
materialLight,
noctisLilac,
solarizedLight,
tokyoNightDay,
xcodeLight,
};
// lineBackground is background with 50% opacity, to make sure the selection below is visible
export const settings = {
strudelTheme: {
background: '#222',
lineBackground: '#22222250',
foreground: '#fff',
// foreground: '#75baff',
caret: '#ffcc00',
selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626',
// lineHighlight: '#8a91991a', // original
lineHighlight: '#00000050',
gutterBackground: 'transparent',
// gutterForeground: '#8a919966',
gutterForeground: '#8a919966',
},
abcdef: {
background: '#0f0f0f',
lineBackground: '#0f0f0f50',
foreground: '#defdef',
caret: '#00FF00',
selection: '#515151',
selectionMatch: '#515151',
gutterBackground: '#555',
gutterForeground: '#FFFFFF',
lineHighlight: '#314151',
},
androidstudio: {
background: '#282b2e',
lineBackground: '#282b2e50',
foreground: '#a9b7c6',
caret: '#00FF00',
selection: '#343739',
selectionMatch: '#343739',
lineHighlight: '#343739',
},
atomone: {
background: '#272C35',
lineBackground: '#272C3550',
foreground: '#9d9b97',
caret: '#797977',
selection: '#ffffff30',
selectionMatch: '#2B323D',
gutterBackground: '#272C35',
gutterForeground: '#465063',
gutterBorder: 'transparent',
lineHighlight: '#2B323D',
},
aura: {
background: '#21202e',
lineBackground: '#21202e50',
foreground: '#edecee',
caret: '#a277ff',
selection: '#3d375e7f',
selectionMatch: '#3d375e7f',
gutterBackground: '#21202e',
gutterForeground: '#edecee',
gutterBorder: 'transparent',
lineHighlight: '#a394f033',
},
bbedit: {
light: true,
background: '#FFFFFF',
lineBackground: '#FFFFFF50',
foreground: '#000000',
caret: '#FBAC52',
selection: '#FFD420',
selectionMatch: '#FFD420',
gutterBackground: '#f5f5f5',
gutterForeground: '#4D4D4C',
gutterBorder: 'transparent',
lineHighlight: '#00000012',
},
bespin: {
background: '#28211c',
lineBackground: '#28211c50',
foreground: '#9d9b97',
caret: '#797977',
selection: '#36312e',
selectionMatch: '#4f382b',
gutterBackground: '#28211c',
gutterForeground: '#666666',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
darcula: {
background: '#2B2B2B',
lineBackground: '#2B2B2B50',
foreground: '#f8f8f2',
caret: '#FFFFFF',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: 'rgba(255, 255, 255, 0.1)',
gutterForeground: '#999',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
dracula: {
background: '#282a36',
lineBackground: '#282a3650',
foreground: '#f8f8f2',
caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)',
gutterBackground: '#282a36',
gutterForeground: '#6D8A88',
gutterBorder: 'transparent',
lineHighlight: 'rgba(255, 255, 255, 0.1)',
},
duotoneLight: {
light: true,
background: '#faf8f5',
lineBackground: '#faf8f550',
foreground: '#b29762',
caret: '#93abdc',
selection: '#e3dcce',
selectionMatch: '#e3dcce',
gutterBackground: '#faf8f5',
gutterForeground: '#cdc4b1',
gutterBorder: 'transparent',
lineHighlight: '#EFEFEF',
},
duotoneDark: {
background: '#2a2734',
lineBackground: '#2a273450',
foreground: '#6c6783',
caret: '#ffad5c',
selection: 'rgba(255, 255, 255, 0.1)',
gutterBackground: '#2a2734',
gutterForeground: '#545167',
lineHighlight: '#36334280',
},
eclipse: {
light: true,
background: '#fff',
lineBackground: '#ffffff50',
foreground: '#000',
caret: '#FFFFFF',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#f7f7f7',
gutterForeground: '#999',
lineHighlight: '#e8f2ff',
gutterBorder: 'transparent',
},
githubLight: {
light: true,
background: '#fff',
lineBackground: '#ffffff50',
foreground: '#24292e',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#6e7781',
},
githubDark: {
background: '#0d1117',
lineBackground: '#0d111750',
foreground: '#c9d1d9',
caret: '#c9d1d9',
selection: '#003d73',
selectionMatch: '#003d73',
lineHighlight: '#36334280',
},
gruvboxDark: {
background: '#282828',
lineBackground: '#28282850',
foreground: '#ebdbb2',
caret: '#ebdbb2',
selection: '#bdae93',
selectionMatch: '#bdae93',
lineHighlight: '#3c3836',
gutterBackground: '#282828',
gutterForeground: '#7c6f64',
},
gruvboxLight: {
light: true,
background: '#fbf1c7',
lineBackground: '#fbf1c750',
foreground: '#3c3836',
caret: '#af3a03',
selection: '#ebdbb2',
selectionMatch: '#bdae93',
lineHighlight: '#ebdbb2',
gutterBackground: '#ebdbb2',
gutterForeground: '#665c54',
gutterBorder: 'transparent',
},
materialDark: {
background: '#2e3235',
lineBackground: '#2e323550',
foreground: '#bdbdbd',
caret: '#a0a4ae',
selection: '#d7d4f0',
selectionMatch: '#d7d4f0',
gutterBackground: '#2e3235',
gutterForeground: '#999',
gutterActiveForeground: '#4f5b66',
lineHighlight: '#545b61',
},
materialLight: {
light: true,
background: '#FAFAFA',
lineBackground: '#FAFAFA50',
foreground: '#90A4AE',
caret: '#272727',
selection: '#80CBC440',
selectionMatch: '#FAFAFA',
gutterBackground: '#FAFAFA',
gutterForeground: '#90A4AE',
gutterBorder: 'transparent',
lineHighlight: '#CCD7DA50',
},
noctisLilac: {
light: true,
background: '#f2f1f8',
lineBackground: '#f2f1f850',
foreground: '#0c006b',
caret: '#5c49e9',
selection: '#d5d1f2',
selectionMatch: '#d5d1f2',
gutterBackground: '#f2f1f8',
gutterForeground: '#0c006b70',
lineHighlight: '#e1def3',
},
nord: {
background: '#2e3440',
lineBackground: '#2e344050',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#3b4252',
selectionMatch: '#e5e9f0',
gutterBackground: '#2e3440',
gutterForeground: '#4c566a',
gutterActiveForeground: '#d8dee9',
lineHighlight: '#4c566a',
},
okaidia: {
background: '#272822',
lineBackground: '#27282250',
foreground: '#FFFFFF',
caret: '#FFFFFF',
selection: '#49483E',
selectionMatch: '#49483E',
gutterBackground: '#272822',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
},
solarizedLight: {
light: true,
background: '#fdf6e3',
lineBackground: '#fdf6e350',
foreground: '#657b83',
caret: '#586e75',
selection: '#dfd9c8',
selectionMatch: '#dfd9c8',
gutterBackground: '#00000010',
gutterForeground: '#657b83',
lineHighlight: '#dfd9c8',
},
solarizedDark: {
background: '#002b36',
lineBackground: '#002b3650',
foreground: '#93a1a1',
caret: '#839496',
selection: '#173541',
selectionMatch: '#aafe661a',
gutterBackground: '#00252f',
gutterForeground: '#839496',
lineHighlight: '#173541',
},
sublime: {
background: '#303841',
lineBackground: '#30384150',
foreground: '#FFFFFF',
caret: '#FBAC52',
selection: '#4C5964',
selectionMatch: '#3A546E',
gutterBackground: '#303841',
gutterForeground: '#FFFFFF70',
lineHighlight: '#00000059',
},
tokyoNightDay: {
light: true,
background: '#e1e2e7',
lineBackground: '#e1e2e750',
foreground: '#3760bf',
caret: '#3760bf',
selection: '#99a7df',
selectionMatch: '#99a7df',
gutterBackground: '#e1e2e7',
gutterForeground: '#3760bf',
gutterBorder: 'transparent',
lineHighlight: '#5f5faf11',
},
tokyoNightStorm: {
background: '#24283b',
lineBackground: '#24283b50',
foreground: '#7982a9',
caret: '#c0caf5',
selection: '#6f7bb630',
selectionMatch: '#1f2335',
gutterBackground: '#24283b',
gutterForeground: '#7982a9',
gutterBorder: 'transparent',
lineHighlight: '#292e42',
},
tokyoNight: {
background: '#1a1b26',
lineBackground: '#1a1b2650',
foreground: '#787c99',
caret: '#c0caf5',
selection: '#515c7e40',
selectionMatch: '#16161e',
gutterBackground: '#1a1b26',
gutterForeground: '#787c99',
gutterBorder: 'transparent',
lineHighlight: '#1e202e',
},
vscodeDark: {
background: '#1e1e1e',
lineBackground: '#1e1e1e50',
foreground: '#9cdcfe',
caret: '#c6c6c6',
selection: '#6199ff2f',
selectionMatch: '#72a1ff59',
lineHighlight: '#ffffff0f',
gutterBackground: '#1e1e1e',
gutterForeground: '#838383',
gutterActiveForeground: '#fff',
fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace',
},
xcodeLight: {
light: true,
background: '#fff',
lineBackground: '#ffffff50',
foreground: '#3D3D3D',
selection: '#BBDFFF',
selectionMatch: '#BBDFFF',
gutterBackground: '#fff',
gutterForeground: '#AFAFAF',
lineHighlight: '#EDF4FF',
},
xcodeDark: {
background: '#292A30',
lineBackground: '#292A3050',
foreground: '#CECFD0',
caret: '#fff',
selection: '#727377',
selectionMatch: '#727377',
lineHighlight: '#2F3239',
},
};
function getColors(str) {
const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})/g;
const colors = [];
let match;
while ((match = colorRegex.exec(str)) !== null) {
const color = match[0];
if (!colors.includes(color)) {
colors.push(color);
}
}
return colors;
}
// TODO: remove
export function themeColors(theme) {
return getColors(stringifySafe(theme));
}
function getCircularReplacer() {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
return;
}
seen.add(value);
}
return value;
};
}
function stringifySafe(json) {
return JSON.stringify(json, getCircularReplacer());
}

View File

@ -1,7 +1,3 @@
body {
background-color: #222;
}
.cm-gutters { .cm-gutters {
display: none !important; display: none !important;
} }

27
website/src/useTheme.jsx Normal file
View 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;

View File

@ -3,6 +3,7 @@
const defaultTheme = require('tailwindcss/defaultTheme'); const defaultTheme = require('tailwindcss/defaultTheme');
module.exports = { module.exports = {
darkMode: 'class',
content: [ content: [
'./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}', './src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}',
'../packages/react/src/**/*.{html,js,jsx,md,mdx,ts,tsx}', '../packages/react/src/**/*.{html,js,jsx,md,mdx,ts,tsx}',
@ -10,18 +11,19 @@ module.exports = {
theme: { theme: {
extend: { extend: {
colors: { colors: {
primary: '#c792ea',
secondary: '#c3e88d',
tertiary: '#82aaff', tertiary: '#82aaff',
highlight: '#ffcc00', highlight: '#bb8800',
linegray: '#8a91991a', // codemirror-theme settings
lineblack: '#00000095', background: 'var(--background)',
bg: '#222222', lineBackground: 'var(--lineBackground)',
// header: '#8a91991a', foreground: 'var(--foreground)',
// footer: '#8a91991a', caret: 'var(--caret)',
header: '#00000050', selection: 'var(--selection)',
// header: 'transparent', selectionMatch: 'var(--selectionMatch)',
footer: '#00000050', gutterBackground: 'var(--gutterBackground)',
gutterForeground: 'var(--gutterForeground)',
gutterBorder: 'var(--gutterBorder)',
lineHighlight: 'var(--lineHighlight)',
}, },
typography(theme) { typography(theme) {
return { return {