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
**/*.tsx
**/*.ts
**/*.json
**/*.json
**/dev-dist
**/dist

View File

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

View File

@ -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;
@ -79,9 +80,17 @@ const highlightField = StateField.define({
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(
(value) => {
onChange?.(value);
@ -106,6 +115,7 @@ export default function CodeMirror({ value, onChange, onViewChanged, onSelection
<>
<_CodeMirror
value={value}
theme={theme || strudelTheme}
onChange={handleOnChange}
onCreateEditor={handleOnCreateEditor}
onUpdate={handleOnUpdate}

View File

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

View File

@ -1,9 +1,9 @@
.container {
@apply rounded-md overflow-hidden bg-[#222222];
@apply overflow-hidden;
}
.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 {
@ -11,11 +11,11 @@
}
.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 {
@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 {

View File

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

View File

@ -1,9 +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';

235
pnpm-lock.yaml generated
View File

@ -373,6 +373,7 @@ importers:
'@types/node': ^18.0.0
'@types/react': ^18.0.26
'@types/react-dom': ^18.0.9
'@uiw/codemirror-themes-all': ^4.19.8
'@vite-pwa/astro': ^0.0.1
astro: ^1.7.2
canvas: ^2.11.0
@ -415,6 +416,7 @@ importers:
'@types/node': 18.11.18
'@types/react': 18.0.27
'@types/react-dom': 18.0.10
'@uiw/codemirror-themes-all': 4.19.8
astro: 1.9.2_@types+node@18.11.18
canvas: 2.11.0
fraction.js: 4.2.0
@ -3994,6 +3996,173 @@ packages:
'@codemirror/view': 6.7.3
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:
resolution: {integrity: sha512-M/42RkPI60ItlssmNuEoZO2MQvlY6fRmdX7XRUAhKjxczZoaq8xS6HIvv1whGf2zGsTrwdVTPCm6ls0l17dvPA==}
peerDependencies:
@ -4005,6 +4174,14 @@ packages:
'@codemirror/view': 6.7.3
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:
resolution: {integrity: sha512-IHvpYWVSdiaHX0Fk6oY6YyAJigDnyvSpWKNUTRzsMNxB+8/wqZ8lior4TprXH0zyLxW5F1+bTyifFFTeg+X3Sw==}
peerDependencies:
@ -10221,6 +10398,17 @@ packages:
- supports-color
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:
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
engines: {node: '>=10.0.0'}
@ -10231,6 +10419,16 @@ packages:
postcss-value-parser: 4.2.0
read-cache: 1.0.0
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:
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
@ -10240,6 +10438,23 @@ packages:
dependencies:
camelcase-css: 2.0.1
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:
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
@ -10257,6 +10472,15 @@ packages:
postcss: 8.4.21
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:
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
engines: {node: '>=12.0'}
@ -10265,6 +10489,7 @@ packages:
dependencies:
postcss: 8.4.21
postcss-selector-parser: 6.0.11
dev: true
/postcss-selector-parser/6.0.10:
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
@ -11873,6 +12098,8 @@ packages:
resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==}
engines: {node: '>=12.13.0'}
hasBin: true
peerDependencies:
postcss: ^8.0.9
dependencies:
arg: 5.0.2
chokidar: 3.5.3
@ -11889,10 +12116,10 @@ packages:
object-hash: 3.0.0
picocolors: 1.0.0
postcss: 8.4.21
postcss-import: 14.1.0_postcss@8.4.21
postcss-js: 4.0.0_postcss@8.4.21
postcss-load-config: 3.1.4_postcss@8.4.21
postcss-nested: 6.0.0_postcss@8.4.21
postcss-import: 14.1.0
postcss-js: 4.0.0
postcss-load-config: 3.1.4
postcss-nested: 6.0.0
postcss-selector-parser: 6.0.11
postcss-value-parser: 4.2.0
quick-lru: 5.1.1

View File

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

View File

@ -1,9 +1,12 @@
---
import { pwaInfo } from 'virtual:pwa-info';
import '../styles/index.css';
import { settings } from '../repl/themes.mjs';
const { BASE_URL } = import.meta.env;
const base = BASE_URL;
const { strudelTheme } = settings;
---
<!-- Global Metadata -->
@ -27,4 +30,55 @@ const base = BASE_URL;
<script src="./make-scrollable-code-focusable.js" is:inline></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} />}
<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];
---
<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">
<SidebarToggle client:idle />
</div> -->
<div class="flex overflow-visible items-center grow" style="overflow:visible">
<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>
<div class="flex space-x-1 items-baseline">
<span class="">strudel</span>

View File

@ -14,7 +14,7 @@ const langCode = 'en'; // getLanguageFromURL(currentPage);
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>
{
Object.entries(sidebar).map(([header, children]) => (
@ -27,8 +27,8 @@ const sidebar = SIDEBAR[langCode];
return (
<li class="">
<a
class={`pl-4 py-0.5 w-full hover:bg-header block${
currentPageMatch === child.link ? ' bg-header' : ''
class={`pl-4 py-0.5 w-full hover:bg-lineHighlight block${
currentPageMatch === child.link ? ' bg-lineHighlight' : ''
}`}
href={url}
aria-current={currentPageMatch === child.link ? 'page' : false}

View File

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

View File

@ -12,7 +12,7 @@ const { headings, githubEditUrl } = Astro.props as Props;
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} />
<MoreMenu editHref={githubEditUrl} />
</nav>

View File

@ -78,9 +78,9 @@ const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[]; curren
<a
href={`${currentPage}#${heading.slug}`}
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]
} ${currentID === heading.slug ? 'bg-header' : ''}`.trim()}
} ${currentID === heading.slug ? 'bg-lineHighlight' : ''}`.trim()}
>
{unescape(heading.text)}
</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 { useEffect, useState } from 'react';
import { prebake } from '../repl/prebake';
import { themes, settings } from '../repl/themes.mjs';
import './MiniRepl.css';
const theme = localStorage.getItem('strudel-theme') || 'strudelTheme';
let modules;
if (typeof window !== 'undefined') {
@ -32,9 +36,18 @@ export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
.then(([res]) => setRepl(() => res.MiniRepl))
.catch((err) => console.error(err));
}, []);
// const { settings } = useTheme();
return Repl ? (
<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>
) : (
<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]">
<a
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>
</a>
@ -36,7 +36,9 @@ export default function MobileNav({ sidebar }) {
as="a"
href={`/${item.link}`}
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',
)}
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}`;
---
<html dir={frontmatter.dir ?? 'ltr'} lang={frontmatter.lang ?? 'en'} class="initial">
<html dir={frontmatter.dir ?? 'ltr'} lang={frontmatter.lang ?? 'en'} class="initial dark">
<head>
<HeadCommon />
<HeadSEO frontmatter={frontmatter} canonicalUrl={canonicalURL} />
@ -31,7 +31,7 @@ const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
</title>
</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">
<header class="max-w-full fixed top-0 w-full z-[100]">
<Header currentPage={currentPage} />

View File

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

View File

@ -1,15 +1,17 @@
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, { useContext, useCallback, useLayoutEffect, useRef, useState } from 'react';
import { useEvent, loadedSamples, ReplContext } from './Repl';
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
import { loadedSamples } from './Repl';
import { Reference } from './Reference';
import { themes, themeColors } from './themes.mjs';
export function Footer({ context }) {
// const [activeFooter, setActiveFooter] = useState('console');
// const { activeFooter, setActiveFooter, isZen } = useContext?.(ReplContext);
const { activeFooter, setActiveFooter, isZen } = context;
const { activeFooter, setActiveFooter, isZen, theme, setTheme } = context;
const footerContent = useRef();
const [log, setLog] = useState([]);
@ -54,8 +56,8 @@ export function Footer({ context }) {
<div
onClick={() => setActiveFooter(name)}
className={cx(
'h-8 px-2 text-white cursor-pointer hover:text-tertiary flex items-center space-x-1 border-b',
activeFooter === name ? 'border-white hover:border-tertiary' : 'border-transparent',
'h-8 px-2 text-foreground cursor-pointer hover:text-tertiary flex items-center space-x-1 border-b',
activeFooter === name ? 'border-foreground hover:border-tertiary' : 'border-transparent',
)}
>
{label || name}
@ -67,16 +69,17 @@ export function Footer({ context }) {
return null;
}
return (
<footer className="bg-footer z-[20]">
<footer className="bg-lineHighlight z-[20]">
<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="samples" />
<FooterTab name="console" />
<FooterTab name="reference" />
<FooterTab name="theme" />
</div>
{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" />
</button>
)}
@ -87,7 +90,7 @@ export function Footer({ context }) {
ref={footerContent}
>
{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>
<span className={cx('animate-spin inline-block select-none')}>🌀</span> welcome
</h3>
@ -132,7 +135,7 @@ export function Footer({ context }) {
</div>
)}
{activeFooter === 'console' && (
<div className="break-all px-4">
<div className="break-all px-4 dark:text-white text-stone-900">
{log.map((l, i) => {
const message = linkify(l.message);
return (
@ -148,8 +151,8 @@ export function Footer({ context }) {
</div>
)}
{activeFooter === 'samples' && (
<div className="break-normal w-full px-4">
<span className="text-white">{loadedSamples.length} banks loaded:</span>
<div className="break-normal w-full px-4 dark:text-white text-stone-900">
<span>{loadedSamples.length} banks loaded:</span>
{loadedSamples.map(([name, samples]) => (
<span key={name} className="cursor-pointer hover:text-tertiary" onClick={() => {}}>
{' '}
@ -165,6 +168,34 @@ export function Footer({ context }) {
</div>
)}
{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>
)}
</footer>

View File

@ -32,7 +32,7 @@ export function Header({ context }) {
id="header"
className={cx(
'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',
)}
>
@ -48,8 +48,7 @@ export function Header({ context }) {
}}
className={cx(
isEmbedded ? 'text-l cursor-pointer' : 'text-xl',
// 'bg-clip-text bg-gradient-to-r from-primary to-secondary text-transparent font-bold',
'text-white font-bold flex space-x-2 items-center',
'text-foreground font-bold flex space-x-2 items-center',
)}
>
<div
@ -66,7 +65,7 @@ export function Header({ context }) {
</h1>
</div>
{!isZen && (
<div className="flex max-w-full overflow-auto text-white ">
<div className="flex max-w-full overflow-auto text-foreground">
<button
onClick={handleTogglePlay}
title={started ? 'stop' : 'play'}

View File

@ -5,16 +5,16 @@ const visibleFunctions = jsdocJson.docs
export function Reference() {
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">
{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> */}
</a>
))}
</div>
<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>
<p>
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;
}
.cm-gutters {
display: none !important;
}
.cm-content {
padding-top: 12px !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 { CodeMirror, cx, flash, useHighlighting, useStrudel } from '@strudel.cycles/react';
import { CodeMirror, cx, flash, useHighlighting, useStrudel, useKeydown } from '@strudel.cycles/react';
import {
getAudioContext,
getLoadedSamples,
@ -22,6 +22,10 @@ import { Header } from './Header';
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';
initAudioOnFirstClick();
@ -109,6 +113,7 @@ export const ReplContext = createContext(null);
export function Repl({ embedded = false }) {
const isEmbedded = embedded || window.location !== window.parent.location;
const [view, setView] = useState(); // codemirror view
const [theme, setTheme] = useState(initialTheme);
const [lastShared, setLastShared] = useState();
const [activeFooter, setActiveFooter] = useState('');
const [isZen, setIsZen] = useState(false);
@ -167,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,
});
//
@ -255,6 +263,8 @@ export function Repl({ embedded = false }) {
handleShare,
isZen,
setIsZen,
theme,
setTheme,
};
return (
// bg-gradient-to-t from-blue-900 to-slate-900
@ -269,6 +279,7 @@ export function Repl({ embedded = false }) {
<Header context={context} />
<section className="grow flex text-gray-100 relative overflow-auto cursor-text pb-0" id="code">
<CodeMirror
theme={themes[theme] || themes.strudelTheme}
value={code}
onChange={handleChangeCode}
onViewChanged={setView}
@ -276,7 +287,7 @@ export function Repl({ embedded = false }) {
/>
</section>
{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 && (
<button
@ -292,15 +303,3 @@ export function Repl({ embedded = false }) {
</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 {
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');
module.exports = {
darkMode: 'class',
content: [
'./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}',
'../packages/react/src/**/*.{html,js,jsx,md,mdx,ts,tsx}',
@ -10,18 +11,19 @@ module.exports = {
theme: {
extend: {
colors: {
primary: '#c792ea',
secondary: '#c3e88d',
tertiary: '#82aaff',
highlight: '#ffcc00',
linegray: '#8a91991a',
lineblack: '#00000095',
bg: '#222222',
// header: '#8a91991a',
// footer: '#8a91991a',
header: '#00000050',
// header: 'transparent',
footer: '#00000050',
highlight: '#bb8800',
// codemirror-theme settings
background: 'var(--background)',
lineBackground: 'var(--lineBackground)',
foreground: 'var(--foreground)',
caret: 'var(--caret)',
selection: 'var(--selection)',
selectionMatch: 'var(--selectionMatch)',
gutterBackground: 'var(--gutterBackground)',
gutterForeground: 'var(--gutterForeground)',
gutterBorder: 'var(--gutterBorder)',
lineHighlight: 'var(--lineHighlight)',
},
typography(theme) {
return {