Compare commits

..

1 Commits

Author SHA1 Message Date
Aria 62bf9abed3 Add retrig option for LFOs 2026-01-19 09:52:49 -08:00
72 changed files with 526 additions and 569 deletions
-5
View File
@@ -1,5 +0,0 @@
{
"devToolbar": {
"enabled": false
}
}
-1
View File
@@ -198,7 +198,6 @@ export function activateTheme(name) {
const themeSettings = settings[name] || settings.strudelTheme; const themeSettings = settings[name] || settings.strudelTheme;
// set css variables // set css variables
themeStyle.innerHTML = `:root { themeStyle.innerHTML = `:root {
color-scheme: ${themeSettings.light ? 'light' : 'dark'};
${Object.entries(themeSettings) ${Object.entries(themeSettings)
// important to override fallback // important to override fallback
.map(([key, value]) => `--${key}: ${value} !important;`) .map(([key, value]) => `--${key}: ${value} !important;`)
+1 -3
View File
@@ -17,15 +17,13 @@ export const settings = {
background: 'white', background: 'white',
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: deepPurple, foreground: deepPurple,
muted: deepPurple + '50',
caret: '#797977', caret: '#797977',
selection: yellowPink, selection: yellowPink,
selectionMatch: '#2B323D', selectionMatch: '#2B323D',
gutterBackground: pinkAccent, gutterBackground: grey,
gutterForeground: lightGrey, gutterForeground: lightGrey,
gutterBorder: 'transparent', gutterBorder: 'transparent',
lineHighlight: pinkAccent, lineHighlight: pinkAccent,
light: true,
}; };
export default createTheme({ export default createTheme({
-1
View File
@@ -31,7 +31,6 @@ const palette = palettes['Sour Watermelon B'];
export const settings = { export const settings = {
background: palette[3], background: palette[3],
foreground: palette[1], foreground: palette[1],
muted: palette[1] + '50',
caret: palette[0], caret: palette[0],
selection: palette[0], selection: palette[0],
selectionMatch: palette[1], selectionMatch: palette[1],
-2
View File
@@ -8,7 +8,6 @@ export const settings = {
background: '#282b2e', background: '#282b2e',
lineBackground: '#282b2e99', lineBackground: '#282b2e99',
foreground: '#a9b7c6', foreground: '#a9b7c6',
muted: '#a9b7c650',
caret: '#00FF00', caret: '#00FF00',
selection: '#343739', selection: '#343739',
selectionMatch: '#343739', selectionMatch: '#343739',
@@ -20,7 +19,6 @@ export default createTheme({
settings: { settings: {
background: '#282b2e', background: '#282b2e',
foreground: '#a9b7c6', foreground: '#a9b7c6',
muted: '#a9b7c650',
caret: '#00FF00', caret: '#00FF00',
selection: '#4e5254', selection: '#4e5254',
selectionMatch: '#4e5254', selectionMatch: '#4e5254',
-1
View File
@@ -12,7 +12,6 @@ export const settings = {
background: hex[0], background: hex[0],
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: hex[1], foreground: hex[1],
muted: hex[2],
selection: hex[2], selection: hex[2],
selectionMatch: hex[0], selectionMatch: hex[0],
gutterBackground: hex[0], gutterBackground: hex[0],
-2
View File
@@ -11,7 +11,6 @@ export const settings = {
background: '#272C35', background: '#272C35',
lineBackground: '#272C3599', lineBackground: '#272C3599',
foreground: 'hsl(220, 14%, 71%)', foreground: 'hsl(220, 14%, 71%)',
muted: 'hsl(220, 14%, 41%)',
caret: '#797977', caret: '#797977',
selection: '#ffffff30', selection: '#ffffff30',
selectionMatch: '#2B323D', selectionMatch: '#2B323D',
@@ -26,7 +25,6 @@ export default createTheme({
settings: { settings: {
background: '#272C35', background: '#272C35',
foreground: '#9d9b97', foreground: '#9d9b97',
muted: 'hsl(220, 14%, 41%)',
caret: '#797977', caret: '#797977',
selection: '#3d4c64', selection: '#3d4c64',
selectionMatch: '#3d4c64', selectionMatch: '#3d4c64',
-2
View File
@@ -5,7 +5,6 @@ export const settings = {
background: '#21202e', background: '#21202e',
lineBackground: '#21202e99', lineBackground: '#21202e99',
foreground: '#edecee', foreground: '#edecee',
muted: '#edecee50',
caret: '#a277ff', caret: '#a277ff',
selection: '#3d375e7f', selection: '#3d375e7f',
selectionMatch: '#3d375e7f', selectionMatch: '#3d375e7f',
@@ -19,7 +18,6 @@ export default createTheme({
settings: { settings: {
background: '#21202e', background: '#21202e',
foreground: '#edecee', foreground: '#edecee',
muted: '#edecee50',
caret: '#a277ff', caret: '#a277ff',
selection: '#5a51898f', selection: '#5a51898f',
selectionMatch: '#5a51898f', selectionMatch: '#5a51898f',
-2
View File
@@ -6,7 +6,6 @@ export const settings = {
background: '#FFFFFF', background: '#FFFFFF',
lineBackground: '#FFFFFF99', lineBackground: '#FFFFFF99',
foreground: '#000000', foreground: '#000000',
muted: '#00000050',
caret: '#FBAC52', caret: '#FBAC52',
selection: '#FFD420', selection: '#FFD420',
selectionMatch: '#FFD420', selectionMatch: '#FFD420',
@@ -21,7 +20,6 @@ export default createTheme({
settings: { settings: {
background: '#FFFFFF', background: '#FFFFFF',
foreground: '#000000', foreground: '#000000',
muted: '#00000050',
caret: '#FBAC52', caret: '#FBAC52',
selection: '#FFD420', selection: '#FFD420',
selectionMatch: '#FFD420', selectionMatch: '#FFD420',
+1 -2
View File
@@ -2,8 +2,7 @@ import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs'; import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: 'black', background: 'black',
foreground: 'white', foreground: 'white', // whats that?
muted: '#ffffff50',
caret: 'white', caret: 'white',
selection: '#ffffff20', selection: '#ffffff20',
selectionMatch: '#036dd626', selectionMatch: '#036dd626',
+1 -2
View File
@@ -3,8 +3,7 @@ import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: '#051DB5', background: '#051DB5',
lineBackground: '#051DB550', lineBackground: '#051DB550',
foreground: 'white', foreground: 'white', // whats that?
muted: '#ffffff50',
caret: 'white', caret: 'white',
selection: 'rgba(128, 203, 196, 0.5)', selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626', selectionMatch: '#036dd626',
-1
View File
@@ -11,7 +11,6 @@ export const settings = {
background: hex[0], background: hex[0],
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: hex[2], foreground: hex[2],
muted: hex[3],
selection: hex[3], selection: hex[3],
selectionMatch: hex[0], selectionMatch: hex[0],
gutterBackground: hex[0], gutterBackground: hex[0],
-2
View File
@@ -9,7 +9,6 @@ export const settings = {
background: '#242424', background: '#242424',
lineBackground: '#24242499', lineBackground: '#24242499',
foreground: '#f8f8f2', foreground: '#f8f8f2',
muted: '#f8f8f250',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: 'rgba(255, 255, 255, 0.1)', selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)', selectionMatch: 'rgba(255, 255, 255, 0.2)',
@@ -24,7 +23,6 @@ export default createTheme({
settings: { settings: {
background: '#242424', background: '#242424',
foreground: '#f8f8f2', foreground: '#f8f8f2',
muted: '#f8f8f250',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: 'rgba(255, 255, 255, 0.1)', selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)', selectionMatch: 'rgba(255, 255, 255, 0.2)',
-2
View File
@@ -11,7 +11,6 @@ export const settings = {
background: '#282a36', background: '#282a36',
lineBackground: '#282a3699', lineBackground: '#282a3699',
foreground: '#f8f8f2', foreground: '#f8f8f2',
muted: '#f8f8f250',
caret: '#f8f8f0', caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)', selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)', selectionMatch: 'rgba(255, 255, 255, 0.2)',
@@ -28,7 +27,6 @@ export default createTheme({
settings: { settings: {
background: '#282a36', background: '#282a36',
foreground: '#f8f8f2', foreground: '#f8f8f2',
muted: '#f8f8f250',
caret: '#f8f8f0', caret: '#f8f8f0',
selection: 'rgba(255, 255, 255, 0.1)', selection: 'rgba(255, 255, 255, 0.1)',
selectionMatch: 'rgba(255, 255, 255, 0.2)', selectionMatch: 'rgba(255, 255, 255, 0.2)',
-2
View File
@@ -10,7 +10,6 @@ export const settings = {
background: '#2a2734', background: '#2a2734',
lineBackground: '#2a273499', lineBackground: '#2a273499',
foreground: '#eeebff', foreground: '#eeebff',
muted: '#eeebff50',
caret: '#ffad5c', caret: '#ffad5c',
selection: 'rgba(255, 255, 255, 0.1)', selection: 'rgba(255, 255, 255, 0.1)',
gutterBackground: '#2a2734', gutterBackground: '#2a2734',
@@ -23,7 +22,6 @@ export default createTheme({
settings: { settings: {
background: '#2a2734', background: '#2a2734',
foreground: '#6c6783', foreground: '#6c6783',
muted: '#eeebff50',
caret: '#ffad5c', caret: '#ffad5c',
selection: '#9a86fd', selection: '#9a86fd',
selectionMatch: '#9a86fd', selectionMatch: '#9a86fd',
-2
View File
@@ -6,7 +6,6 @@ export const settings = {
background: '#fff', background: '#fff',
lineBackground: '#ffffff99', lineBackground: '#ffffff99',
foreground: '#000', foreground: '#000',
muted: '#00000050',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: '#d7d4f0', selection: '#d7d4f0',
selectionMatch: '#d7d4f0', selectionMatch: '#d7d4f0',
@@ -21,7 +20,6 @@ export default createTheme({
settings: { settings: {
background: '#fff', background: '#fff',
foreground: '#000', foreground: '#000',
muted: '#00000050',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: '#d7d4f0', selection: '#d7d4f0',
selectionMatch: '#d7d4f0', selectionMatch: '#d7d4f0',
-1
View File
@@ -23,7 +23,6 @@ export const settings = {
background: hex[0], background: hex[0],
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: hex[10], foreground: hex[10],
muted: hex[7],
selection: hex[8], selection: hex[8],
selectionMatch: hex[0], selectionMatch: hex[0],
gutterBackground: hex[3], gutterBackground: hex[3],
-2
View File
@@ -5,7 +5,6 @@ export const settings = {
background: '#0d1117', background: '#0d1117',
lineBackground: '#0d111799', lineBackground: '#0d111799',
foreground: '#c9d1d9', foreground: '#c9d1d9',
muted: '#c9d1d950',
caret: '#c9d1d9', caret: '#c9d1d9',
selection: '#003d73', selection: '#003d73',
selectionMatch: '#003d73', selectionMatch: '#003d73',
@@ -17,7 +16,6 @@ export default createTheme({
settings: { settings: {
background: '#0d1117', background: '#0d1117',
foreground: '#c9d1d9', foreground: '#c9d1d9',
muted: '#c9d1d950',
caret: '#c9d1d9', caret: '#c9d1d9',
selection: '#003d73', selection: '#003d73',
selectionMatch: '#003d73', selectionMatch: '#003d73',
-2
View File
@@ -6,7 +6,6 @@ export const settings = {
background: '#fff', background: '#fff',
lineBackground: '#ffffff99', lineBackground: '#ffffff99',
foreground: '#24292e', foreground: '#24292e',
muted: '#24292e50',
selection: '#BBDFFF', selection: '#BBDFFF',
selectionMatch: '#BBDFFF', selectionMatch: '#BBDFFF',
gutterBackground: '#fff', gutterBackground: '#fff',
@@ -18,7 +17,6 @@ export default createTheme({
settings: { settings: {
background: '#fff', background: '#fff',
foreground: '#24292e', foreground: '#24292e',
muted: '#24292e50',
selection: '#BBDFFF', selection: '#BBDFFF',
selectionMatch: '#BBDFFF', selectionMatch: '#BBDFFF',
gutterBackground: '#fff', gutterBackground: '#fff',
-1
View File
@@ -13,7 +13,6 @@ export const settings = {
background: hex[0], background: hex[0],
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: hex[2], foreground: hex[2],
muted: hex[2] + '50',
selection: hex[4], selection: hex[4],
selectionMatch: hex[0], selectionMatch: hex[0],
gutterBackground: hex[0], gutterBackground: hex[0],
-2
View File
@@ -10,7 +10,6 @@ export const settings = {
background: '#282828', background: '#282828',
lineBackground: '#28282899', lineBackground: '#28282899',
foreground: '#ebdbb2', foreground: '#ebdbb2',
muted: '#ebdbb250',
caret: '#ebdbb2', caret: '#ebdbb2',
selection: '#bdae93', selection: '#bdae93',
selectionMatch: '#bdae93', selectionMatch: '#bdae93',
@@ -24,7 +23,6 @@ export default createTheme({
settings: { settings: {
background: '#282828', background: '#282828',
foreground: '#ebdbb2', foreground: '#ebdbb2',
muted: '#ebdbb250',
caret: '#ebdbb2', caret: '#ebdbb2',
selection: '#b99d555c', selection: '#b99d555c',
selectionMatch: '#b99d555c', selectionMatch: '#b99d555c',
-2
View File
@@ -11,7 +11,6 @@ export const settings = {
background: '#fbf1c7', background: '#fbf1c7',
lineBackground: '#fbf1c799', lineBackground: '#fbf1c799',
foreground: '#3c3836', foreground: '#3c3836',
muted: '#3c383650',
caret: '#af3a03', caret: '#af3a03',
selection: '#ebdbb2', selection: '#ebdbb2',
selectionMatch: '#bdae93', selectionMatch: '#bdae93',
@@ -26,7 +25,6 @@ export default createTheme({
settings: { settings: {
background: '#fbf1c7', background: '#fbf1c7',
foreground: '#3c3836', foreground: '#3c3836',
muted: '#3c383650',
caret: '#af3a03', caret: '#af3a03',
selection: '#bdae9391', selection: '#bdae9391',
selectionMatch: '#bdae9391', selectionMatch: '#bdae9391',
-2
View File
@@ -5,7 +5,6 @@ export const settings = {
background: '#212121', background: '#212121',
lineBackground: '#21212199', lineBackground: '#21212199',
foreground: '#bdbdbd', foreground: '#bdbdbd',
muted: '#bdbdbd50',
caret: '#a0a4ae', caret: '#a0a4ae',
selection: '#d7d4f0', selection: '#d7d4f0',
selectionMatch: '#d7d4f0', selectionMatch: '#d7d4f0',
@@ -20,7 +19,6 @@ export default createTheme({
settings: { settings: {
background: '#212121', background: '#212121',
foreground: '#bdbdbd', foreground: '#bdbdbd',
muted: '#bdbdbd50',
caret: '#a0a4ae', caret: '#a0a4ae',
selection: '#d7d4f063', selection: '#d7d4f063',
selectionMatch: '#d7d4f063', selectionMatch: '#d7d4f063',
+1 -3
View File
@@ -5,8 +5,7 @@ export const settings = {
light: true, light: true,
background: '#FAFAFA', background: '#FAFAFA',
lineBackground: '#FAFAFA99', lineBackground: '#FAFAFA99',
foreground: '#6182B8', foreground: '#90A4AE',
muted: '#6182B850',
caret: '#272727', caret: '#272727',
selection: '#80CBC440', selection: '#80CBC440',
selectionMatch: '#FAFAFA', selectionMatch: '#FAFAFA',
@@ -20,7 +19,6 @@ export default createTheme({
settings: { settings: {
background: '#FAFAFA', background: '#FAFAFA',
foreground: '#90A4AE', foreground: '#90A4AE',
muted: '#6182B850',
caret: '#272727', caret: '#272727',
selection: '#80CBC440', selection: '#80CBC440',
selectionMatch: '#80CBC440', selectionMatch: '#80CBC440',
-2
View File
@@ -5,7 +5,6 @@ export const settings = {
background: '#272822', background: '#272822',
lineBackground: '#27282299', lineBackground: '#27282299',
foreground: '#FFFFFF', foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: '#49483E', selection: '#49483E',
selectionMatch: '#49483E', selectionMatch: '#49483E',
@@ -19,7 +18,6 @@ export default createTheme({
settings: { settings: {
background: '#272822', background: '#272822',
foreground: '#FFFFFF', foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: '#49483E', selection: '#49483E',
selectionMatch: '#49483E', selectionMatch: '#49483E',
-2
View File
@@ -6,7 +6,6 @@ export const settings = {
background: '#f2f1f8', background: '#f2f1f8',
lineBackground: '#f2f1f899', lineBackground: '#f2f1f899',
foreground: '#0c006b', foreground: '#0c006b',
muted: '#0c006b50',
caret: '#5c49e9', caret: '#5c49e9',
selection: '#d5d1f2', selection: '#d5d1f2',
selectionMatch: '#d5d1f2', selectionMatch: '#d5d1f2',
@@ -20,7 +19,6 @@ export default createTheme({
settings: { settings: {
background: '#f2f1f8', background: '#f2f1f8',
foreground: '#0c006b', foreground: '#0c006b',
muted: '#0c006b50',
caret: '#5c49e9', caret: '#5c49e9',
selection: '#d5d1f2', selection: '#d5d1f2',
selectionMatch: '#d5d1f2', selectionMatch: '#d5d1f2',
-2
View File
@@ -5,7 +5,6 @@ export const settings = {
background: '#2e3440', background: '#2e3440',
lineBackground: '#2e344099', lineBackground: '#2e344099',
foreground: '#FFFFFF', foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: '#3b4252', selection: '#3b4252',
selectionMatch: '#e5e9f0', selectionMatch: '#e5e9f0',
@@ -21,7 +20,6 @@ export default createTheme({
settings: { settings: {
background: '#2e3440', background: '#2e3440',
foreground: '#FFFFFF', foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FFFFFF', caret: '#FFFFFF',
selection: '#00000073', selection: '#00000073',
selectionMatch: '#00000073', selectionMatch: '#00000073',
-1
View File
@@ -13,7 +13,6 @@ export const settings = {
background: hex[0], background: hex[0],
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: hex[2], foreground: hex[2],
muted: hex[2] + '50',
selection: hex[4], selection: hex[4],
selectionMatch: hex[0], selectionMatch: hex[0],
gutterBackground: hex[0], gutterBackground: hex[0],
-2
View File
@@ -5,7 +5,6 @@ export const settings = {
background: '#002b36', background: '#002b36',
lineBackground: '#002b3699', lineBackground: '#002b3699',
foreground: '#93a1a1', foreground: '#93a1a1',
muted: '#93a1a150',
caret: '#839496', caret: '#839496',
selection: '#173541', selection: '#173541',
selectionMatch: '#aafe661a', selectionMatch: '#aafe661a',
@@ -46,7 +45,6 @@ export default createTheme({
settings: { settings: {
background: c.background, background: c.background,
foreground: c.foreground, foreground: c.foreground,
muted: c.muted,
caret: c.cursor, caret: c.cursor,
selection: c.selection, selection: c.selection,
selectionMatch: c.selection, selectionMatch: c.selection,
-2
View File
@@ -8,7 +8,6 @@ export const settings = {
background: '#fdf6e3', background: '#fdf6e3',
lineBackground: '#fdf6e399', lineBackground: '#fdf6e399',
foreground: '#657b83', foreground: '#657b83',
muted: '#657b8350',
caret: '#586e75', caret: '#586e75',
selection: '#dfd9c8', selection: '#dfd9c8',
selectionMatch: '#dfd9c8', selectionMatch: '#dfd9c8',
@@ -20,7 +19,6 @@ export const settings = {
const c = { const c = {
background: '#FDF6E3', background: '#FDF6E3',
foreground: '#657B83', foreground: '#657B83',
muted: '#657b8350',
selection: '#EEE8D5', selection: '#EEE8D5',
selectionMatch: '#EEE8D5', selectionMatch: '#EEE8D5',
cursor: '#657B83', cursor: '#657B83',
-1
View File
@@ -13,7 +13,6 @@ export const settings = {
background: '#000000', background: '#000000',
lineBackground: 'transparent', lineBackground: 'transparent',
foreground: hex[4], foreground: hex[4],
muted: hex[6],
selection: hex[6], selection: hex[6],
gutterBackground: hex[0], gutterBackground: hex[0],
gutterForeground: hex[5], gutterForeground: hex[5],
-1
View File
@@ -5,7 +5,6 @@ export const settings = {
background: '#222', background: '#222',
lineBackground: '#22222299', lineBackground: '#22222299',
foreground: '#fff', foreground: '#fff',
muted: '#8a919966',
caret: '#ffcc00', caret: '#ffcc00',
selection: 'rgba(128, 203, 196, 0.5)', selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#036dd626', selectionMatch: '#036dd626',
-2
View File
@@ -5,7 +5,6 @@ export const settings = {
background: '#303841', background: '#303841',
lineBackground: '#30384199', lineBackground: '#30384199',
foreground: '#FFFFFF', foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FBAC52', caret: '#FBAC52',
selection: '#4C5964', selection: '#4C5964',
selectionMatch: '#3A546E', selectionMatch: '#3A546E',
@@ -19,7 +18,6 @@ export default createTheme({
settings: { settings: {
background: '#303841', background: '#303841',
foreground: '#FFFFFF', foreground: '#FFFFFF',
muted: '#FFFFFF50',
caret: '#FBAC52', caret: '#FBAC52',
selection: '#4C5964', selection: '#4C5964',
selectionMatch: '#3A546E', selectionMatch: '#3A546E',
+1 -2
View File
@@ -9,8 +9,7 @@ let colorD = '#f8fc55';
export const settings = { export const settings = {
background: '#000000', background: '#000000',
foreground: colorA, foreground: colorA, // whats that?
muted: colorA + '50',
caret: colorC, caret: colorC,
selection: colorD, selection: colorD,
selectionMatch: colorA, selectionMatch: colorA,
+2 -4
View File
@@ -4,8 +4,7 @@ import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: '#24283b', background: '#24283b',
lineBackground: '#24283b99', lineBackground: '#24283b99',
foreground: '#f9f2f9', foreground: '#7982a9',
muted: '#f9f2f950',
caret: '#c0caf5', caret: '#c0caf5',
selection: '#6f7bb630', selection: '#6f7bb630',
selectionMatch: '#1f2335', selectionMatch: '#1f2335',
@@ -19,8 +18,7 @@ export default createTheme({
theme: 'dark', theme: 'dark',
settings: { settings: {
background: '#24283b', background: '#24283b',
foreground: '#f9f2f9', foreground: '#7982a9',
muted: '#f9f2f950',
caret: '#c0caf5', caret: '#c0caf5',
selection: '#6f7bb630', selection: '#6f7bb630',
selectionMatch: '#343b5f', selectionMatch: '#343b5f',
+2 -4
View File
@@ -4,8 +4,7 @@ import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: '#1a1b26', background: '#1a1b26',
lineBackground: '#1a1b2699', lineBackground: '#1a1b2699',
foreground: '#f8fcf9', foreground: '#787c99',
muted: '#f8fcf950',
caret: '#c0caf5', caret: '#c0caf5',
selection: '#515c7e40', selection: '#515c7e40',
selectionMatch: '#16161e', selectionMatch: '#16161e',
@@ -19,8 +18,7 @@ export default createTheme({
theme: 'dark', theme: 'dark',
settings: { settings: {
background: '#1a1b26', background: '#1a1b26',
foreground: '#f8fcf9', foreground: '#787c99',
muted: '#f8fcf950',
caret: '#c0caf5', caret: '#c0caf5',
selection: '#515c7e40', selection: '#515c7e40',
selectionMatch: '#16161e', selectionMatch: '#16161e',
-2
View File
@@ -6,7 +6,6 @@ export const settings = {
background: '#e1e2e7', background: '#e1e2e7',
lineBackground: '#e1e2e799', lineBackground: '#e1e2e799',
foreground: '#3760bf', foreground: '#3760bf',
muted: '#3760bf50',
caret: '#3760bf', caret: '#3760bf',
selection: '#99a7df', selection: '#99a7df',
selectionMatch: '#99a7df', selectionMatch: '#99a7df',
@@ -21,7 +20,6 @@ export default createTheme({
settings: { settings: {
background: '#e1e2e7', background: '#e1e2e7',
foreground: '#3760bf', foreground: '#3760bf',
muted: '#3760bf50',
caret: '#3760bf', caret: '#3760bf',
selection: '#99a7df', selection: '#99a7df',
selectionMatch: '#99a7df', selectionMatch: '#99a7df',
-2
View File
@@ -5,7 +5,6 @@ export const settings = {
background: '#1e1e1e', background: '#1e1e1e',
lineBackground: '#1e1e1e99', lineBackground: '#1e1e1e99',
foreground: '#fff', foreground: '#fff',
muted: '#ffffff50',
caret: '#c6c6c6', caret: '#c6c6c6',
selection: '#6199ff2f', selection: '#6199ff2f',
selectionMatch: '#72a1ff59', selectionMatch: '#72a1ff59',
@@ -20,7 +19,6 @@ export default createTheme({
settings: { settings: {
background: '#1e1e1e', background: '#1e1e1e',
foreground: '#fff', foreground: '#fff',
muted: '#ffffff50',
caret: '#c6c6c6', caret: '#c6c6c6',
selection: '#6199ff2f', selection: '#6199ff2f',
selectionMatch: '#72a1ff59', selectionMatch: '#72a1ff59',
-2
View File
@@ -5,7 +5,6 @@ export const settings = {
background: '#ffffff', background: '#ffffff',
lineBackground: '#ffffff50', lineBackground: '#ffffff50',
foreground: '#383a42', foreground: '#383a42',
muted: '#383a4250',
caret: '#000', caret: '#000',
selection: '#add6ff', selection: '#add6ff',
selectionMatch: '#a8ac94', selectionMatch: '#a8ac94',
@@ -21,7 +20,6 @@ export default createTheme({
settings: { settings: {
background: '#ffffff', background: '#ffffff',
foreground: '#383a42', foreground: '#383a42',
muted: '#383a4250',
caret: '#000', caret: '#000',
selection: '#add6ff', selection: '#add6ff',
selectionMatch: '#a8ac94', selectionMatch: '#a8ac94',
+1 -2
View File
@@ -2,8 +2,7 @@ import { tags as t } from '@lezer/highlight';
import { createTheme } from './theme-helper.mjs'; import { createTheme } from './theme-helper.mjs';
export const settings = { export const settings = {
background: 'white', background: 'white',
foreground: 'black', foreground: 'black', // whats that?
muted: '#00000050',
caret: 'black', caret: 'black',
selection: 'rgba(128, 203, 196, 0.5)', selection: 'rgba(128, 203, 196, 0.5)',
selectionMatch: '#ffffff26', selectionMatch: '#ffffff26',
-2
View File
@@ -6,7 +6,6 @@ export const settings = {
background: '#fff', background: '#fff',
lineBackground: '#ffffff99', lineBackground: '#ffffff99',
foreground: '#3D3D3D', foreground: '#3D3D3D',
muted: '#3D3D3D50',
selection: '#BBDFFF', selection: '#BBDFFF',
selectionMatch: '#BBDFFF', selectionMatch: '#BBDFFF',
gutterBackground: '#fff', gutterBackground: '#fff',
@@ -19,7 +18,6 @@ export default createTheme({
settings: { settings: {
background: '#fff', background: '#fff',
foreground: '#3D3D3D', foreground: '#3D3D3D',
muted: '#3D3D3D50',
selection: '#BBDFFF', selection: '#BBDFFF',
selectionMatch: '#BBDFFF', selectionMatch: '#BBDFFF',
gutterBackground: '#fff', gutterBackground: '#fff',
+3 -1
View File
@@ -3046,6 +3046,7 @@ registerSubControls('lfo', [
['skew', 'sk'], ['skew', 'sk'],
['curve', 'cu'], ['curve', 'cu'],
['sync', 's'], ['sync', 's'],
['retrig', 'rt'],
['fxi'], ['fxi'],
]); ]);
registerSubControls('env', [ registerSubControls('env', [
@@ -3132,13 +3133,14 @@ Pattern.prototype.modulate = function (type, config, idPat) {
* @param {string | Pattern} [config.control] Node to modulate. Aliases: c * @param {string | Pattern} [config.control] Node to modulate. Aliases: c
* @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc * @param {string | Pattern} [config.subControl] Sub-control name to append to the control key. Aliases: sc
* @param {number | Pattern} [config.rate] Modulation rate. Aliases: r * @param {number | Pattern} [config.rate] Modulation rate. Aliases: r
* @param {number | Pattern} [config.sync] Tempo-synced modulation rate. Aliases: s
* @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr * @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
* @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da * @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
* @param {number | Pattern} [config.dcoffset] DC offset / bias for the waveform. Aliases: dc * @param {number | Pattern} [config.dcoffset] DC offset / bias for the waveform. Aliases: dc
* @param {number | Pattern} [config.shape] Shape index. Aliases: sh * @param {number | Pattern} [config.shape] Shape index. Aliases: sh
* @param {number | Pattern} [config.skew] Skew amount. Aliases: sk * @param {number | Pattern} [config.skew] Skew amount. Aliases: sk
* @param {number | Pattern} [config.curve] Exponential curve amount. Aliases: cu * @param {number | Pattern} [config.curve] Exponential curve amount. Aliases: cu
* @param {number | Pattern} [config.sync] Tempo-synced modulation rate. Aliases: s * @param {number | Pattern} [config.retrig] If > 0.5, the LFO will retrigger on each event. Aliases: rt
* @param {number | Pattern} [config.fxi] FX index to target * @param {number | Pattern} [config.fxi] FX index to target
* @param {string | Pattern} id ID to use for this modulator * @param {string | Pattern} id ID to use for this modulator
* @returns Pattern * @returns Pattern
+2 -1
View File
@@ -98,6 +98,7 @@ export const connectLFO = (id, params, nodeTracker) => {
fxi = 'main', fxi = 'main',
depth = 1, depth = 1,
depthabs, depthabs,
retrig = 0,
...filteredParams ...filteredParams
} = params; } = params;
const { targetParams, paramName } = getTargetParamsForControl(control, nodeTracker[fxi], subControl); const { targetParams, paramName } = getTargetParamsForControl(control, nodeTracker[fxi], subControl);
@@ -109,7 +110,7 @@ export const connectLFO = (id, params, nodeTracker) => {
const modParams = { const modParams = {
...filteredParams, ...filteredParams,
frequency: sync !== undefined ? sync * cps : rate, frequency: sync !== undefined ? sync * cps : rate,
time: cycle / cps, time: retrig > 0.5 ? 0 : cycle / cps,
depth: depthValue, depth: depthValue,
min, min,
max, max,
-1
View File
@@ -33,7 +33,6 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
<!-- the following variables are just a fallback to make sure everything is readable without JS --> <!-- the following variables are just a fallback to make sure everything is readable without JS -->
<style is:global> <style is:global>
:root { :root {
color-scheme: dark;
--background: #222; --background: #222;
--lineBackground: #22222299; --lineBackground: #22222299;
--foreground: #fff; --foreground: #fff;
@@ -32,7 +32,6 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
<!-- the following variables are just a fallback to make sure everything is readable without JS --> <!-- the following variables are just a fallback to make sure everything is readable without JS -->
<style is:global> <style is:global>
:root { :root {
color-scheme: dark;
--background: #222; --background: #222;
--lineBackground: #22222299; --lineBackground: #22222299;
--foreground: #fff; --foreground: #fff;
+2 -2
View File
@@ -1,5 +1,5 @@
import Loader from '@src/repl/components/Loader'; import Loader from '@src/repl/components/Loader';
import { BottomPanel } from '@src/repl/components/panel/Panel'; import { HorizontalPanel } from '@src/repl/components/panel/Panel';
import { Code } from '@src/repl/components/Code'; import { Code } from '@src/repl/components/Code';
import BigPlayButton from '@src/repl/components/BigPlayButton'; import BigPlayButton from '@src/repl/components/BigPlayButton';
import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage'; import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage';
@@ -20,7 +20,7 @@ export default function UdelsEditor(Props) {
<Code containerRef={containerRef} editorRef={editorRef} init={init} /> <Code containerRef={containerRef} editorRef={editorRef} init={init} />
</div> </div>
<UserFacingErrorMessage error={error} /> <UserFacingErrorMessage error={error} />
<BottomPanel context={context} /> <HorizontalPanel context={context} />
</div> </div>
); );
} }
+12
View File
@@ -1,3 +1,15 @@
:root {
--background: #222;
--lineBackground: #22222299;
--foreground: #fff;
--caret: #ffcc00;
--selection: rgba(128, 203, 196, 0.5);
--selectionMatch: #036dd626;
--lineHighlight: #00000050;
--gutterBackground: transparent;
--gutterForeground: #8a919966;
}
.darken::before { .darken::before {
content: ' '; content: ' ';
position: fixed; position: fixed;
+1 -1
View File
@@ -8,7 +8,7 @@ export function Code(Props) {
return ( return (
<section <section
className={'text-gray-100 cursor-text pb-0 overflow-auto grow z-10'} className={'text-gray-100 cursor-text pb-0 overflow-auto grow'}
id="code" id="code"
ref={(el) => { ref={(el) => {
containerRef.current = el; containerRef.current = el;
@@ -2,7 +2,7 @@ import Loader from '@src/repl/components/Loader';
import { Code } from '@src/repl/components/Code'; import { Code } from '@src/repl/components/Code';
import BigPlayButton from '@src/repl/components/BigPlayButton'; import BigPlayButton from '@src/repl/components/BigPlayButton';
import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage'; import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage';
import { MainPanel } from './panel/Panel'; import { Header } from './Header';
// type Props = { // type Props = {
// context: replcontext, // context: replcontext,
@@ -14,7 +14,7 @@ export default function EmbeddedReplEditor(Props) {
return ( return (
<div className="h-full flex flex-col relative" {...editorProps}> <div className="h-full flex flex-col relative" {...editorProps}>
<Loader active={pending} /> <Loader active={pending} />
<MainPanel context={context} embedded={true} /> <Header context={context} embedded={true} />
<BigPlayButton started={started} handleTogglePlay={handleTogglePlay} /> <BigPlayButton started={started} handleTogglePlay={handleTogglePlay} />
<div className="grow flex relative overflow-hidden"> <div className="grow flex relative overflow-hidden">
<Code containerRef={containerRef} editorRef={editorRef} init={init} /> <Code containerRef={containerRef} editorRef={editorRef} init={init} />
+150
View File
@@ -0,0 +1,150 @@
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
import StopCircleIcon from '@heroicons/react/20/solid/StopCircleIcon';
import cx from '@src/cx.mjs';
import { useSettings, setIsZen } from '../../settings.mjs';
import '../Repl.css';
const { BASE_URL } = import.meta.env;
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL;
export function Header({ context, embedded = false }) {
const { started, pending, isDirty, activeCode, handleTogglePlay, handleEvaluate, handleShuffle, handleShare } =
context;
const isEmbedded = typeof window !== 'undefined' && (embedded || window.location !== window.parent.location);
const { isZen, isButtonRowHidden, isCSSAnimationDisabled, fontFamily } = useSettings();
return (
<header
id="header"
className={cx(
'flex-none text-black z-[100] text-lg select-none h-20 md:h-14',
!isZen && !isEmbedded && 'bg-lineHighlight',
isZen ? 'h-12 w-8 fixed top-0 left-0' : 'sticky top-0 w-full py-1 justify-between',
isEmbedded ? 'flex' : 'md:flex',
)}
style={{ fontFamily }}
>
<div className="px-4 flex space-x-2 md:pt-0 select-none">
<h1
onClick={() => {
if (isEmbedded) window.open(window.location.href.replace('embed', ''));
}}
className={cx(
isEmbedded ? 'text-l cursor-pointer' : 'text-xl',
'text-foreground font-bold flex space-x-2 items-center',
)}
>
<div
className={cx(
'mt-[1px]',
started && !isCSSAnimationDisabled && 'animate-spin',
'cursor-pointer text-blue-500',
isZen && 'fixed top-2 right-4',
)}
onClick={() => {
if (!isEmbedded) {
setIsZen(!isZen);
}
}}
>
<span className="block text-foreground rotate-90"></span>
</div>
{!isZen && (
<div className="space-x-2">
<span className="">strudel</span>
<span className="text-sm font-medium">REPL</span>
{!isEmbedded && isButtonRowHidden && (
<a href={`${baseNoTrailing}/learn`} className="text-sm opacity-25 font-medium">
DOCS
</a>
)}
</div>
)}
</h1>
</div>
{!isZen && !isButtonRowHidden && (
<div className="flex max-w-full overflow-auto text-foreground px-1 md:px-2">
<button
onClick={handleTogglePlay}
title={started ? 'stop' : 'play'}
className={cx(
!isEmbedded ? 'p-2' : 'px-2',
'hover:opacity-50',
!started && !isCSSAnimationDisabled && 'animate-pulse',
)}
>
{!pending ? (
<span className={cx('flex items-center space-x-2')}>
{started ? <StopCircleIcon className="w-6 h-6" /> : <PlayCircleIcon className="w-6 h-6" />}
{!isEmbedded && <span>{started ? 'stop' : 'play'}</span>}
</span>
) : (
<>loading...</>
)}
</button>
<button
onClick={handleEvaluate}
title="update"
className={cx(
'flex items-center space-x-1',
!isEmbedded ? 'p-2' : 'px-2',
!isDirty || !activeCode ? 'opacity-50' : 'hover:opacity-50',
)}
>
{!isEmbedded && <span>update</span>}
</button>
{/* !isEmbedded && (
<button
title="shuffle"
className="hover:opacity-50 p-2 flex items-center space-x-1"
onClick={handleShuffle}
>
<span> shuffle</span>
</button>
) */}
{!isEmbedded && (
<button
title="share"
className={cx(
'cursor-pointer hover:opacity-50 flex items-center space-x-1',
!isEmbedded ? 'p-2' : 'px-2',
)}
onClick={handleShare}
>
<span>share</span>
</button>
)}
{!isEmbedded && (
<a
title="learn"
href={`${baseNoTrailing}/workshop/getting-started/`}
className={cx('hover:opacity-50 flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
>
<span>learn</span>
</a>
)}
{/* {isEmbedded && (
<button className={cx('hover:opacity-50 px-2')}>
<a href={window.location.href} target="_blank" rel="noopener noreferrer" title="Open in REPL">
🚀
</a>
</button>
)}
{isEmbedded && (
<button className={cx('hover:opacity-50 px-2')}>
<a
onClick={() => {
window.location.href = initialUrl;
window.location.reload();
}}
title="Reset"
>
💔
</a>
</button>
)} */}
</div>
)}
</header>
);
}
+8 -12
View File
@@ -1,7 +1,8 @@
import { Code } from '@src/repl/components/Code';
import Loader from '@src/repl/components/Loader'; import Loader from '@src/repl/components/Loader';
import { BottomPanel, MainPanel, RightPanel } from '@src/repl/components/panel/Panel'; import { HorizontalPanel, VerticalPanel } from '@src/repl/components/panel/Panel';
import { Code } from '@src/repl/components/Code';
import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage'; import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage';
import { Header } from './Header';
import { useSettings } from '@src/settings.mjs'; import { useSettings } from '@src/settings.mjs';
// type Props = { // type Props = {
@@ -13,22 +14,17 @@ export default function ReplEditor(Props) {
const { containerRef, editorRef, error, init, pending } = context; const { containerRef, editorRef, error, init, pending } = context;
const settings = useSettings(); const settings = useSettings();
const { panelPosition, isZen } = settings; const { panelPosition, isZen } = settings;
const isEmbedded = typeof window !== 'undefined' && window.location !== window.parent.location;
return ( return (
<div className="h-full flex flex-col relative" {...editorProps}> <div className="h-full flex flex-col relative" {...editorProps}>
<Loader active={pending} /> <Loader active={pending} />
<div className="flex flex-col grow overflow-hidden"> <Header context={context} />
{/* <MainPanel context={context} isEmbedded={isEmbedded} className="hidden sm:block" /> */} <div className="grow flex relative overflow-hidden">
<MainPanel context={context} isEmbedded={isEmbedded} /> <Code containerRef={containerRef} editorRef={editorRef} init={init} />
<div className="flex overflow-hidden h-full"> {!isZen && panelPosition === 'right' && <VerticalPanel context={context} />}
<Code containerRef={containerRef} editorRef={editorRef} init={init} />
{!isZen && panelPosition === 'right' && <RightPanel context={context} />}
</div>
</div> </div>
<UserFacingErrorMessage error={error} /> <UserFacingErrorMessage error={error} />
{!isZen && panelPosition === 'bottom' && <BottomPanel context={context} />} {!isZen && panelPosition === 'bottom' && <HorizontalPanel context={context} />}
{/* <MainPanel context={context} isEmbedded={isEmbedded} className="block sm:hidden" /> */}
</div> </div>
); );
} }
@@ -2,7 +2,7 @@ import cx from '@src/cx.mjs';
export function ActionButton({ children, label, labelIsHidden, className, ...buttonProps }) { export function ActionButton({ children, label, labelIsHidden, className, ...buttonProps }) {
return ( return (
<button className={cx('hover:opacity-50 text-xs text-nowrap w-fit', className)} title={label} {...buttonProps}> <button className={cx('hover:opacity-50 text-nowrap w-fit', className)} title={label} {...buttonProps}>
{labelIsHidden !== true && label} {labelIsHidden !== true && label}
{children} {children}
</button> </button>
@@ -13,13 +13,16 @@ export function SpecialActionButton(props) {
const { className, ...buttonProps } = props; const { className, ...buttonProps } = props;
return ( return (
<ActionButton {...buttonProps} className={cx('bg-background p-2 max-w-[300px] hover:opacity-50', className)} /> <ActionButton
{...buttonProps}
className={cx('bg-background p-2 max-w-[300px] rounded-md hover:opacity-50', className)}
/>
); );
} }
export function ActionInput({ label, className, ...props }) { export function ActionInput({ label, className, ...props }) {
return ( return (
<label className={cx('inline-flex items-center cursor-pointer ', className)}> <label className={cx('inline-flex items-center cursor-pointer', className)}>
<input {...props} className="sr-only peer" /> <input {...props} className="sr-only peer" />
<span className="inline-flex items-center peer-hover:opacity-50">{label}</span> <span className="inline-flex items-center peer-hover:opacity-50">{label}</span>
@@ -32,7 +35,7 @@ export function SpecialActionInput({ className, ...props }) {
<ActionInput <ActionInput
{...props} {...props}
className={className} className={className}
label={<span className="bg-background p-2 max-w-[300px]">{props.label}</span>} label={<span className="bg-background p-2 max-w-[300px] rounded-md">{props.label}</span>}
/> />
); );
} }
@@ -1,19 +0,0 @@
export function SidebarIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-panel-right-icon lucide-panel-right"
>
<rect width="18" height="18" x="3" y="3" rx="2" />
<path d="M15 3v18" />
</svg>
);
}
@@ -1,13 +0,0 @@
// Source: https://commons.wikimedia.org/wiki/File:CHAM_PUNCTUATION_SPIRAL.svg
// License: SIL Open Font License, Version 1.1. The license text is available at https://openfontlicense.org/open-font-license-official-text/
export function StrudelIcon({ className = '' }) {
return (
<svg className={className} width="100%" height="100%" viewBox="0 0 213 213">
<g>
<g id="_1" serif:id="1">
<path d="M199.905,102.467c-0,17.959 -3.695,33.45 -11.078,46.468c-7.369,13.024 -17.9,23.079 -31.586,30.173c-13.673,7.093 -29.994,10.64 -48.961,10.64c-19.964,0 -37.183,-3.874 -51.66,-11.617c-14.482,-7.749 -25.592,-18.775 -33.334,-33.103c-7.749,-14.322 -11.617,-31.407 -11.617,-51.248l17.143,-0c-0,16.95 3.187,31.503 9.561,43.666c6.373,12.15 15.485,21.48 27.346,27.989c11.874,6.509 26.061,9.766 42.561,9.766c15.485,0 28.798,-2.898 39.94,-8.687c11.154,-5.789 19.693,-14.11 25.624,-24.956c5.943,-10.858 8.918,-23.812 8.918,-38.86c-0,-20.85 -5.5,-37.202 -16.5,-49.063c-11,-11.874 -26.344,-17.811 -46.031,-17.811c-17.515,-0 -31.266,4.883 -41.251,14.649c-9.991,9.767 -14.983,23.337 -14.983,40.711c-0,15.781 4.015,28.13 12.054,37.035c8.031,8.893 19.07,13.339 33.103,13.339c12.452,0 22.186,-3.431 29.196,-10.306c7.023,-6.888 10.538,-16.333 10.538,-28.348c-0,-10.281 -2.57,-18.415 -7.711,-24.416c-5.14,-6.014 -12.413,-9.021 -21.82,-9.021c-7.813,-0 -13.93,2.178 -18.351,6.528c-4.407,4.337 -6.605,10.197 -6.605,17.579c0,1.44 0.064,2.815 0.206,4.112c4.485,-7.363 11.141,-11.051 19.97,-11.051c5.5,-0 9.875,1.561 13.133,4.678c3.27,3.103 4.909,7.324 4.909,12.67c-0,5.796 -1.966,10.506 -5.886,14.136c-3.906,3.617 -9.265,5.423 -16.063,5.423c-8.533,-0 -15.337,-2.814 -20.407,-8.456c-5.076,-5.654 -7.607,-13.114 -7.607,-22.386c-0,-11.135 3.431,-19.957 10.306,-26.472c6.888,-6.528 16.121,-9.792 27.706,-9.792c13.467,0 24.03,4.164 31.689,12.491c7.672,8.327 11.515,19.584 11.515,33.771c-0,16.057 -4.884,28.753 -14.65,38.089c-9.767,9.343 -23.195,14.008 -40.274,14.008c-12.594,-0 -23.491,-2.603 -32.692,-7.814c-9.188,-5.204 -16.243,-12.625 -21.178,-22.257c-4.921,-9.625 -7.376,-21.023 -7.376,-34.182c0,-14.342 2.93,-26.64 8.79,-36.907c5.873,-10.281 14.271,-18.203 25.187,-23.774c10.93,-5.584 23.928,-8.379 38.989,-8.379c16.359,0 30.469,3.258 42.33,9.767c11.874,6.515 20.985,15.819 27.346,27.911c6.374,12.08 9.561,26.518 9.561,43.307Zm-86.408,-6.939c-7.235,-0 -11.867,4.491 -13.904,13.467c2.756,3.033 6.528,4.549 11.308,4.549c7.659,0 11.489,-3.11 11.489,-9.329c-0,-2.757 -0.797,-4.89 -2.39,-6.4c-1.581,-1.523 -3.746,-2.287 -6.503,-2.287Z" />
</g>
</g>
</svg>
);
}
@@ -1,4 +1,4 @@
import { Textbox } from '@src/repl/components/panel/SettingsTab'; import { Textbox } from '../textbox/Textbox';
import cx from '@src/cx.mjs'; import cx from '@src/cx.mjs';
function IncButton({ children, className, ...buttonProps }) { function IncButton({ children, className, ...buttonProps }) {
@@ -1,6 +1,6 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { SelectInputDuplicate } from '@src/repl/components/panel/SettingsTab'; import { SelectInput } from './SelectInput';
import { getAudioDevices } from '@strudel/webaudio'; import { getAudioDevices } from '@strudel/webaudio';
const initdevices = new Map(); const initdevices = new Map();
@@ -29,7 +29,7 @@ export function AudioDeviceSelector({ audioDeviceName, onChange, isDisabled }) {
options.set(deviceName, deviceName); options.set(deviceName, deviceName);
}); });
return ( return (
<SelectInputDuplicate <SelectInput
isDisabled={isDisabled} isDisabled={isDisabled}
options={options} options={options}
onClick={onClick} onClick={onClick}
@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { audioEngineTargets } from '../../../settings.mjs'; import { audioEngineTargets } from '../../../settings.mjs';
import { SelectInputDuplicate } from '@src/repl/components/panel/SettingsTab'; import { SelectInput } from './SelectInput';
// Allows the user to select an audio interface for Strudel to play through // Allows the user to select an audio interface for Strudel to play through
export function AudioEngineTargetSelector({ target, onChange, isDisabled }) { export function AudioEngineTargetSelector({ target, onChange, isDisabled }) {
@@ -12,8 +12,8 @@ export function AudioEngineTargetSelector({ target, onChange, isDisabled }) {
[audioEngineTargets.osc, audioEngineTargets.osc], [audioEngineTargets.osc, audioEngineTargets.osc],
]); ]);
return ( return (
<div className="flex flex-col gap-1"> <div className=" flex flex-col gap-1">
<SelectInputDuplicate isDisabled={isDisabled} options={options} value={target} onChange={onTargetChange} /> <SelectInput isDisabled={isDisabled} options={options} value={target} onChange={onTargetChange} />
{target === audioEngineTargets.osc && ( {target === audioEngineTargets.osc && (
<div> <div>
<p className="text-sm italic"> <p className="text-sm italic">
@@ -2,23 +2,13 @@ import cx from '@src/cx.mjs';
import { useSettings } from '../../../settings.mjs'; import { useSettings } from '../../../settings.mjs';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { $strudel_log_history } from '../useLogger'; import { $strudel_log_history } from '../useLogger';
import { useEffect, useRef } from 'react';
export function ConsoleTab() { export function ConsoleTab() {
const log = useStore($strudel_log_history); const log = useStore($strudel_log_history);
const { fontFamily } = useSettings(); const { fontFamily } = useSettings();
const scrollRef = useRef();
// scroll to bottom when log changes
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [log]);
return ( return (
<div id="console-tab" className="break-all w-full h-full" style={{ fontFamily }}> <div id="console-tab" className="break-all w-full first-line:text-sm p-2 h-full" style={{ fontFamily }}>
<div className="h-full w-full overflow-auto space-y-1 p-2 rounded-md" ref={scrollRef}> <div className="bg-background h-full w-full overflow-auto space-y-1 p-2 rounded-md">
{' '}
{/* bg-background */}
{log.map((l, i) => { {log.map((l, i) => {
const message = linkify(l.message); const message = linkify(l.message);
const color = l.data?.hap?.value?.color; const color = l.data?.hap?.value?.color;
@@ -26,13 +16,12 @@ export function ConsoleTab() {
<div <div
key={l.id} key={l.id}
className={cx( className={cx(
'whitespace-nowrap',
l.type === 'error' ? 'text-background bg-foreground' : 'text-foreground', l.type === 'error' ? 'text-background bg-foreground' : 'text-foreground',
l.type === 'highlight' && 'underline', l.type === 'highlight' && 'underline',
)} )}
style={color ? { color } : {}} style={color ? { color } : {}}
> >
<span dangerouslySetInnerHTML={{ __html: message }} className="whitespace-nowrap" /> <span dangerouslySetInnerHTML={{ __html: message }} />
{l.count ? ` (${l.count})` : ''} {l.count ? ` (${l.count})` : ''}
</div> </div>
); );
@@ -1,7 +1,10 @@
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
import cx from '@src/cx.mjs'; import cx from '@src/cx.mjs';
import { useState } from 'react'; import NumberInput from '@src/repl/components/NumberInput';
import { Textbox } from '@src/repl/components/panel/SettingsTab'; import { useEffect, useState } from 'react';
import { Textbox } from '../textbox/Textbox';
import { getAudioContext } from '@strudel/webaudio'; import { getAudioContext } from '@strudel/webaudio';
import XMarkIcon from '@heroicons/react/24/outline/XMarkIcon';
function Checkbox({ label, value, onChange, disabled = false }) { function Checkbox({ label, value, onChange, disabled = false }) {
return ( return (
@@ -45,7 +48,7 @@ export default function ExportTab(Props) {
return ( return (
<> <>
<div className="text-foreground w-full space-y-4 p-4"> <div className="text-foreground w-full p-4 space-y-4">
<FormItem label="File name" disabled={exporting}> <FormItem label="File name" disabled={exporting}>
<Textbox <Textbox
onBlur={(e) => { onBlur={(e) => {
@@ -56,7 +59,7 @@ export default function ExportTab(Props) {
}} }}
disabled={exporting} disabled={exporting}
placeholder="Leave empty to use current date" placeholder="Leave empty to use current date"
className={cx('placeholder-muted', exporting && 'opacity-50 border-opacity-50')} className={cx('placeholder:opacity-50', exporting && 'opacity-50 border-opacity-50')}
value={downloadName ?? ''} value={downloadName ?? ''}
/> />
</FormItem> </FormItem>
+3 -3
View File
@@ -1,15 +1,15 @@
import cx from '@src/cx.mjs'; import cx from '@src/cx.mjs';
export function ButtonGroup({ value, onChange, items, wrap = false }) { export function ButtonGroup({ value, onChange, items }) {
return ( return (
<div className={cx('flex max-w-lg space-x-0 text-xs', wrap && 'flex-wrap')}> <div className="flex max-w-lg">
{Object.entries(items).map(([key, label], i, arr) => ( {Object.entries(items).map(([key, label], i, arr) => (
<button <button
key={key} key={key}
id={key} id={key}
onClick={() => onChange(key)} onClick={() => onChange(key)}
className={cx( className={cx(
'px-2 border-b-2 h-8 whitespace-nowrap border-box max-h-8 hover:opacity-50', 'px-2 border-b h-8 whitespace-nowrap',
// i === 0 && 'rounded-l-md', // i === 0 && 'rounded-l-md',
// i === arr.length - 1 && 'rounded-r-md', // i === arr.length - 1 && 'rounded-r-md',
// value === key ? 'bg-background' : 'bg-lineHighlight', // value === key ? 'bg-background' : 'bg-lineHighlight',
+109 -197
View File
@@ -1,204 +1,77 @@
import { Bars3Icon, PlayIcon, StopIcon, XMarkIcon } from '@heroicons/react/16/solid';
import cx from '@src/cx.mjs'; import cx from '@src/cx.mjs';
import { StrudelIcon } from '@src/repl/components/icons/StrudelIcon'; import { setPanelPinned, setActiveFooter as setTab, setIsPanelOpened, useSettings } from '../../../settings.mjs';
import { useSettings, setIsZen, setIsPanelOpened, setActiveFooter as setTab } from '../../../settings.mjs';
import '../../Repl.css';
import { useLogger } from '../useLogger';
import { ConsoleTab } from './ConsoleTab'; import { ConsoleTab } from './ConsoleTab';
import ExportTab from './ExportTab';
import { FilesTab } from './FilesTab'; import { FilesTab } from './FilesTab';
import { PatternsTab } from './PatternsTab';
import { Reference } from './Reference'; import { Reference } from './Reference';
import { SettingsTab } from './SettingsTab'; import { SettingsTab } from './SettingsTab';
import { SoundsTab } from './SoundsTab'; import { SoundsTab } from './SoundsTab';
import { useLogger } from '../useLogger';
import { WelcomeTab } from './WelcomeTab'; import { WelcomeTab } from './WelcomeTab';
import { PatternsTab } from './PatternsTab';
import { ChevronLeftIcon, XMarkIcon } from '@heroicons/react/16/solid';
import ExportTab from './ExportTab';
const TAURI = typeof window !== 'undefined' && window.__TAURI__; const TAURI = typeof window !== 'undefined' && window.__TAURI__;
const { BASE_URL } = import.meta.env; export function HorizontalPanel({ context }) {
const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL; const settings = useSettings();
const { isPanelOpen, activeFooter: tab } = settings;
export function LogoButton({ context, isEmbedded }) {
const { started } = context;
const { isZen, isCSSAnimationDisabled, fontFamily } = useSettings();
return (
<div
className={cx(
'mt-[1px]',
started && !isCSSAnimationDisabled && 'animate-spin',
'cursor-pointer text-blue-500',
isZen && 'fixed top-2 right-4',
)}
onClick={() => {
if (!isEmbedded) {
setIsZen(!isZen);
}
}}
>
<span className="block text-foreground rotate-90">
<StrudelIcon className="w-5 h-5 fill-foreground" />
</span>
</div>
);
}
export function MainPanel({ context, isEmbedded = false, className }) {
const { isZen, isButtonRowHidden, fontFamily } = useSettings();
return (
<nav
id="header"
className={cx(
'flex-none text-black z-[100] text-sm select-none min-h-10 max-h-10',
!isZen && !isEmbedded && 'border-b border-muted bg-lineHighlight',
isZen ? 'h-12 w-8 fixed top-0 left-0' : '',
'flex items-center',
className,
)}
style={{ fontFamily }}
>
<div className={cx('flex w-full justify-between')}>
<div className="px-3 py-1 flex space-x-2 select-none">
<h1
onClick={() => {
if (isEmbedded) window.open(window.location.href.replace('embed', ''));
}}
className={cx(
isEmbedded ? 'text-l cursor-pointer' : 'text-xl',
'text-foreground font-bold flex space-x-2 items-center',
)}
>
<LogoButton context={context} isEmbedded={isEmbedded} />
{!isZen && (
<div className="space-x-2 flex items-baseline">
<span className="hidden sm:block">strudel</span>
<span className="text-sm font-medium hidden sm:block">REPL</span>
</div>
)}
</h1>
</div>
{!isZen && (
<div className="flex grow justify-end">
{!isButtonRowHidden && <MainMenu isEmbedded={isEmbedded} context={context} />}
<PanelToggle isEmbedded={isEmbedded} isZen={isZen} />
</div>
)}
</div>
</nav>
);
}
export function Footer({ context, isEmbedded = false }) {
return (
<div className="border-t border-muted bg-lineHighlight block lg:hidden">
<MainMenu context={context} isEmbedded={isEmbedded} />
</div>
);
}
function MainMenu({ context, isEmbedded = false, className }) {
const { started, pending, isDirty, activeCode, handleTogglePlay, handleEvaluate, handleShare } = context;
const { isCSSAnimationDisabled } = useSettings();
return (
<div className={cx('flex text-sm max-w-full shrink-0 overflow-hidden text-foreground px-2 h-10', className)}>
<button
onClick={handleTogglePlay}
title={started ? 'stop' : 'play'}
className={cx('px-2 hover:opacity-50', !started && !isCSSAnimationDisabled && 'animate-pulse')}
>
<span className={cx('flex items-center space-x-2')}>
{started ? <StopIcon className="w-5 h-5" /> : <PlayIcon className="w-5 h-5" />}
{!isEmbedded && <span>{pending ? '...' : started ? 'stop' : 'play'}</span>}
</span>
</button>
<button
onClick={handleEvaluate}
title="update"
className={cx('flex items-center space-x-1 px-2', !isDirty || !activeCode ? 'opacity-50' : 'hover:opacity-50')}
>
{!isEmbedded && <span>update</span>}
</button>
{!isEmbedded && (
<button
title="share"
className={cx('cursor-pointer hover:opacity-50 flex items-center space-x-1 px-2')}
onClick={handleShare}
>
<span>share</span>
</button>
)}
{!isEmbedded && (
<a
title="learn"
href={`${baseNoTrailing}/workshop/getting-started/`}
className={cx('hover:opacity-50 flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
>
<span>learn</span>
</a>
)}
</div>
);
}
function PanelCloseButton() {
const { isPanelOpen } = useSettings();
return (
isPanelOpen && (
<button
onClick={() => setIsPanelOpened(false)}
className={cx('px-2 py-0 text-foreground hover:opacity-50')}
aria-label="Close Menu"
>
<XMarkIcon className="w-6 h-6" />
</button>
)
);
}
export function BottomPanel({ context }) {
const { isPanelOpen, activeFooter: tab } = useSettings();
return ( return (
<PanelNav <PanelNav
className={cx( settings={settings}
isPanelOpen ? `min-h-[360px] max-h-[360px]` : 'min-h-10 max-h-10', className={cx(isPanelOpen ? `min-h-[360px] max-h-[360px]` : 'min-h-12 max-h-12', 'overflow-hidden flex flex-col')}
'overflow-hidden flex flex-col relative',
)}
> >
<div className="flex justify-between min-h-10 max-h-10 grid-cols-2 items-center border-t border-muted">
<PanelCloseButton />
<Tabs setTab={setTab} tab={tab} className={cx(isPanelOpen && 'border-l border-muted')} />
</div>
{isPanelOpen && ( {isPanelOpen && (
<div className="w-full h-full overflow-auto border-t border-muted"> <div className="flex h-full overflow-auto pr-10 ">
<PanelContent context={context} tab={tab} /> <PanelContent context={context} tab={tab} />
</div> </div>
)} )}
<div className="absolute right-4 pt-4">
<PanelActionButton settings={settings} />
</div>
<div className="flex justify-between min-h-12 max-h-12 grid-cols-2 items-center">
<Tabs setTab={setTab} tab={tab} />
</div>
</PanelNav> </PanelNav>
); );
} }
export function RightPanel({ context }) { export function VerticalPanel({ context }) {
const settings = useSettings(); const settings = useSettings();
const { activeFooter: tab, isPanelOpen } = settings; const { activeFooter: tab, isPanelOpen } = settings;
if (!isPanelOpen) {
return;
}
return ( return (
<PanelNav <PanelNav
settings={settings} settings={settings}
className={cx( className={cx(isPanelOpen ? `min-w-[min(600px,80vw)] max-w-[min(600px,80vw)]` : 'min-w-12 max-w-12')}
'border-l border-muted shrink-0 h-full overflow-hidden',
isPanelOpen ? `min-w-[min(600px,100vw)] max-w-[min(600px,80vw)]` : 'min-w-12 max-w-12',
)}
> >
<div className={cx('flex flex-col h-full')}> {isPanelOpen ? (
<div className="flex justify-between w-full overflow-hidden border-b border-muted min-h-10 max-h-10"> <div className={cx('flex flex-col h-full')}>
<PanelCloseButton /> <div className="flex justify-between w-full ">
<Tabs setTab={setTab} tab={tab} className="border-l border-muted" /> <Tabs setTab={setTab} tab={tab} />
<PanelActionButton settings={settings} />
</div>
<div className="overflow-auto h-full">
<PanelContent context={context} tab={tab} />
</div>
</div> </div>
<div className="overflow-auto h-full"> ) : (
<PanelContent context={context} tab={tab} /> <button
</div> onClick={(e) => {
</div> setIsPanelOpened(true);
}}
aria-label="open menu panel"
className={cx(
'flex flex-col hover:bg-lineBackground items-center cursor-pointer justify-center w-full h-full',
)}
>
<ChevronLeftIcon className="text-foreground opacity-50 w-6 h-6" />
</button>
)}
</PanelNav> </PanelNav>
); );
} }
@@ -216,8 +89,8 @@ if (TAURI) {
tabNames.files = 'files'; tabNames.files = 'files';
} }
function PanelNav({ children, className, ...props }) { function PanelNav({ children, className, settings, ...props }) {
const settings = useSettings(); const isHoverBehavior = settings.togglePanelTrigger === 'hover';
return ( return (
<nav <nav
onClick={() => { onClick={() => {
@@ -225,8 +98,18 @@ function PanelNav({ children, className, ...props }) {
setIsPanelOpened(true); setIsPanelOpened(true);
} }
}} }}
onMouseEnter={() => {
if (isHoverBehavior && !settings.isPanelOpen) {
setIsPanelOpened(true);
}
}}
onMouseLeave={() => {
if (isHoverBehavior && !settings.isPanelPinned) {
setIsPanelOpened(false);
}
}}
aria-label="Menu Panel" aria-label="Menu Panel"
className={cx('h-full bg-lineHighlight group overflow-x-auto', className)} className={cx('bg-lineHighlight group overflow-x-auto', className)}
{...props} {...props}
> >
{children} {children}
@@ -262,7 +145,7 @@ function PanelTab({ label, isSelected, onClick }) {
<button <button
onClick={onClick} onClick={onClick}
className={cx( className={cx(
'h-10 px-2 text-sm border-t-2 border-t-transparent text-foreground cursor-pointer hover:opacity-50 flex items-center space-x-1 border-b-2', 'h-8 px-2 text-foreground cursor-pointer hover:opacity-50 flex items-center space-x-1 border-b',
isSelected ? 'border-foreground' : 'border-transparent', isSelected ? 'border-foreground' : 'border-transparent',
)} )}
> >
@@ -271,36 +154,65 @@ function PanelTab({ label, isSelected, onClick }) {
</> </>
); );
} }
function Tabs({ className }) { function Tabs({ setTab, tab, className }) {
const { isPanelOpen, activeFooter: tab } = useSettings();
return ( return (
<div <div className={cx('flex select-none max-w-full overflow-auto pb-2', className)}>
className={cx(
'px-2 w-full flex select-none max-w-full h-10 max-h-10 min-h-10 overflow-auto items-center',
className,
)}
>
{Object.keys(tabNames).map((key) => { {Object.keys(tabNames).map((key) => {
const val = tabNames[key]; const val = tabNames[key];
return <PanelTab key={key} isSelected={tab === val && isPanelOpen} label={key} onClick={() => setTab(val)} />; return <PanelTab key={key} isSelected={tab === val} label={key} onClick={() => setTab(val)} />;
})} })}
</div> </div>
); );
} }
export function PanelToggle({ isEmbedded, isZen }) { function PanelActionButton({ settings }) {
const { panelPosition, isPanelOpen } = useSettings(); const { togglePanelTrigger, isPanelPinned, isPanelOpen } = settings;
const isHoverBehavior = togglePanelTrigger === 'hover';
if (!isPanelOpen) {
return;
}
if (isHoverBehavior) {
return <PinButton pinned={isPanelPinned} />;
}
return <CloseButton onClick={() => setIsPanelOpened(false)} />;
}
function PinButton({ pinned }) {
return ( return (
!isEmbedded && <button
!isZen && onClick={() => setPanelPinned(!pinned)}
panelPosition === 'right' && ( className={cx(
<button 'text-foreground max-h-8 min-h-8 max-w-8 min-w-8 items-center justify-center p-1.5 group-hover:flex',
title="menu" pinned ? 'flex' : 'hidden',
className={cx('border-l border-muted px-2 py-0 text-foreground hover:opacity-50')} )}
onClick={() => setIsPanelOpened(!isPanelOpen)} aria-label="Pin Menu Panel"
>
<svg
stroke="currentColor"
fill={'currentColor'}
strokeWidth="0"
className="w-full h-full"
opacity={pinned ? 1 : '.3'}
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
> >
<Bars3Icon className="w-6 h-6" /> <path d="M9.828.722a.5.5 0 0 1 .354.146l4.95 4.95a.5.5 0 0 1 0 .707c-.48.48-1.072.588-1.503.588-.177 0-.335-.018-.46-.039l-3.134 3.134a6 6 0 0 1 .16 1.013c.046.702-.032 1.687-.72 2.375a.5.5 0 0 1-.707 0l-2.829-2.828-3.182 3.182c-.195.195-1.219.902-1.414.707s.512-1.22.707-1.414l3.182-3.182-2.828-2.829a.5.5 0 0 1 0-.707c.688-.688 1.673-.767 2.375-.72a6 6 0 0 1 1.013.16l3.134-3.133a3 3 0 0 1-.04-.461c0-.43.108-1.022.589-1.503a.5.5 0 0 1 .353-.146"></path>
</button> </svg>
) </button>
);
}
function CloseButton({ onClick }) {
return (
<button
onClick={onClick}
className={cx(
'text-foreground max-h-8 min-h-8 max-w-8 min-w-8 items-center justify-center p-1.5 group-hover:flex',
)}
aria-label="Close Menu"
>
<XMarkIcon />
</button>
); );
} }
@@ -8,7 +8,7 @@ import {
useViewingPatternData, useViewingPatternData,
userPattern, userPattern,
} from '../../../user_pattern_utils.mjs'; } from '../../../user_pattern_utils.mjs';
import { useMemo, useRef } from 'react'; import { useMemo } from 'react';
import { getMetadata } from '../../../metadata_parser.js'; import { getMetadata } from '../../../metadata_parser.js';
import { useExamplePatterns } from '../../useExamplePatterns.jsx'; import { useExamplePatterns } from '../../useExamplePatterns.jsx';
import { parseJSON, isUdels } from '../../util.mjs'; import { parseJSON, isUdels } from '../../util.mjs';
@@ -18,7 +18,7 @@ import { Pagination } from '../pagination/Pagination.jsx';
import { useState } from 'react'; import { useState } from 'react';
import { useDebounce } from '../usedebounce.jsx'; import { useDebounce } from '../usedebounce.jsx';
import cx from '@src/cx.mjs'; import cx from '@src/cx.mjs';
import { Textbox } from '@src/repl/components/panel/SettingsTab.jsx'; import { Textbox } from '../textbox/Textbox.jsx';
export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) { export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
const meta = useMemo(() => getMetadata(pattern.code), [pattern]); const meta = useMemo(() => getMetadata(pattern.code), [pattern]);
@@ -43,7 +43,7 @@ function PatternButton({ showOutline, onClick, pattern, showHiglight }) {
className={cx( className={cx(
'mr-4 hover:opacity-50 cursor-pointer block', 'mr-4 hover:opacity-50 cursor-pointer block',
showOutline && 'outline outline-1', showOutline && 'outline outline-1',
showHiglight && 'ring-selection', showHiglight && 'bg-selection',
)} )}
onClick={onClick} onClick={onClick}
> >
@@ -53,10 +53,11 @@ function PatternButton({ showOutline, onClick, pattern, showHiglight }) {
} }
function PatternButtons({ patterns, activePattern, onClick, started }) { function PatternButtons({ patterns, activePattern, onClick, started }) {
const viewingPatternData = useViewingPatternData(); const viewingPatternStore = useViewingPatternData();
const viewingPatternData = parseJSON(viewingPatternStore);
const viewingPatternID = viewingPatternData.id; const viewingPatternID = viewingPatternData.id;
return ( return (
<div className="p-2"> <div className="">
{Object.values(patterns) {Object.values(patterns)
.reverse() .reverse()
.map((pattern) => { .map((pattern) => {
@@ -82,8 +83,8 @@ const updateCodeWindow = (context, patternData, reset = false) => {
export function PatternsTab({ context }) { export function PatternsTab({ context }) {
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const activePattern = useActivePattern(); const activePattern = useActivePattern();
const viewingPatternData = useViewingPatternData(); const viewingPatternStore = useViewingPatternData();
const viewingPatternData = parseJSON(viewingPatternStore);
const { userPatterns, patternAutoStart } = useSettings(); const { userPatterns, patternAutoStart } = useSettings();
const viewingPatternID = viewingPatternData?.id; const viewingPatternID = viewingPatternData?.id;
@@ -120,71 +121,74 @@ export function PatternsTab({ context }) {
); );
}), }),
); );
}, [search, userPatterns]); }, [search, viewingPatternStore]);
const importRef = useRef();
return ( return (
<div className="w-full h-full text-foreground flex flex-col overflow-hidden"> <div className="px-4 w-full text-foreground space-y-2 flex flex-col overflow-hidden max-h-full h-full">
<Textbox className="w-full border-0" placeholder="Search..." value={search} onChange={setSearch} /> <div className="w-full flex">
<div className="px-2 shrink-0 h-8 space-x-4 flex max-w-full overflow-x-auto border-y border-muted"> <Textbox className="w-full" placeholder="Search" value={search} onChange={setSearch} />
<ActionButton
label="new"
onClick={() => {
const { data } = userPattern.createAndAddToDB();
updateCodeWindow(context, data);
}}
/>
<ActionButton
label="duplicate"
onClick={() => {
const { data } = userPattern.duplicate(viewingPatternData);
updateCodeWindow(context, data);
}}
/>
<ActionButton
label="delete"
onClick={() => {
const { data } = userPattern.delete(viewingPatternID);
updateCodeWindow(context, { ...data, collection: userPattern.collection });
}}
/>
<input
ref={importRef}
style={{ display: 'none' }}
type="file"
multiple
accept="text/plain,text/x-markdown,application/json"
onChange={(e) => importPatterns(e.target.files)}
/>
<ActionButton label="import" onClick={() => importRef.current.click()} />
<ActionButton label="export" onClick={exportPatterns} />
<ActionButton
label="delete-all"
onClick={() => {
const { data } = userPattern.clearAll();
updateCodeWindow(context, data);
}}
/>
</div> </div>
<div className="flex flex-col gap-2 flex-grow overflow-hidden h-full pb-2 ">
<div className="pr-4 space-x-4 flex max-w-full overflow-x-auto">
<ActionButton
label="new"
onClick={() => {
const { data } = userPattern.createAndAddToDB();
updateCodeWindow(context, data);
}}
/>
<ActionButton
label="duplicate"
onClick={() => {
const { data } = userPattern.duplicate(viewingPatternData);
updateCodeWindow(context, data);
}}
/>
<ActionButton
label="delete"
onClick={() => {
const { data } = userPattern.delete(viewingPatternID);
updateCodeWindow(context, { ...data, collection: userPattern.collection });
}}
/>
<label className="hover:opacity-50 cursor-pointer">
<input
style={{ display: 'none' }}
type="file"
multiple
accept="text/plain,text/x-markdown,application/json"
onChange={(e) => importPatterns(e.target.files)}
/>
import
</label>
<ActionButton label="export" onClick={exportPatterns} />
<div className="overflow-auto"> <ActionButton
{/* bg-background */} label="delete-all"
{/* {patternFilter === patternFilterName.user && ( */} onClick={() => {
<PatternButtons const { data } = userPattern.clearAll();
onClick={(id) => { updateCodeWindow(context, data);
updateCodeWindow(context, { ...userPatterns[id], collection: userPattern.collection }, patternAutoStart); }}
/>
</div>
if (context.started && activePattern === id) { <div className="overflow-auto h-full bg-background p-2 rounded-md">
context.handleEvaluate(); {/* {patternFilter === patternFilterName.user && ( */}
} <PatternButtons
}} onClick={(id) => {
patterns={visiblePatterns} updateCodeWindow(context, { ...userPatterns[id], collection: userPattern.collection }, patternAutoStart);
started={context.started}
activePattern={activePattern} if (context.started && activePattern === id) {
viewingPatternID={viewingPatternID} context.handleEvaluate();
/> }
{/* )} */} }}
patterns={visiblePatterns}
started={context.started}
activePattern={activePattern}
viewingPatternID={viewingPatternID}
/>
{/* )} */}
</div>
</div> </div>
</div> </div>
); );
+40 -52
View File
@@ -1,8 +1,7 @@
import { memo, useEffect, useMemo, useState, Fragment } from 'react'; import { memo, useEffect, useMemo, useState, Fragment } from 'react';
import jsdocJson from '../../../../../doc.json'; import jsdocJson from '../../../../../doc.json';
import { Textbox } from '@src/repl/components/panel/SettingsTab'; import { Textbox } from '../textbox/Textbox';
import { settingsMap, useSettings } from '@src/settings.mjs';
const isValid = ({ name, description }) => name && !name.startsWith('_') && !!description; const isValid = ({ name, description }) => name && !name.startsWith('_') && !!description;
@@ -38,19 +37,6 @@ const availableFunctions = (() => {
return functions.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name)); return functions.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name));
})(); })();
const tagCounts = {};
// const tagOptions = { all: `all (${availableFunctions.length})` };
const tagOptions = { all: `all` };
for (const doc of availableFunctions) {
(doc.tags || ['untagged']).forEach((t) => {
if (typeof t === 'string' && t) {
tagCounts[t] = (tagCounts[t] || 0) + 1;
//tagOptions[t] = `${t} (${tagCounts[t]})`;
tagOptions[t] = t;
}
});
}
const getInnerText = (html) => { const getInnerText = (html) => {
var div = document.createElement('div'); var div = document.createElement('div');
div.innerHTML = html; div.innerHTML = html;
@@ -59,38 +45,40 @@ const getInnerText = (html) => {
export const Reference = memo(function Reference() { export const Reference = memo(function Reference() {
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
const [selectedTag, setSelectedTag] = useState(null);
const [selectedFunction, setSelectedFunction] = useState(null); const [selectedFunction, setSelectedFunction] = useState(null);
const { referenceTag } = useSettings();
const toggleTag = (tag) => { const toggleTag = (tag) => {
if (referenceTag === tag) { if (selectedTag === tag) {
setReferenceTag('all'); setSelectedTag(null);
} else { } else {
setReferenceTag(tag); setSelectedTag(tag);
} }
}; };
const searchVisibleFunctions = useMemo(() => { const searchVisibleFunctions = useMemo(() => {
return availableFunctions.filter((entry) => { return availableFunctions.filter((entry) => {
if (referenceTag && referenceTag !== 'all') { if (selectedTag) {
if (!(entry.tags || ['untagged']).includes(referenceTag)) { if (!(entry.tags || ['untagged']).includes(selectedTag)) {
return false; return false;
} }
} }
if (!search) { if (!search) {
return true; return true;
} }
const lowerCaseSearch = search.toLowerCase(); const lowerCaseSearch = search.toLowerCase();
return ( return (
entry.name.toLowerCase().includes(lowerCaseSearch) || entry.name.toLowerCase().includes(lowerCaseSearch) ||
(entry.synonyms?.some((s) => s.toLowerCase().includes(lowerCaseSearch)) ?? false) (entry.synonyms?.some((s) => s.toLowerCase().includes(lowerCaseSearch)) ?? false)
); );
}); });
}, [search, referenceTag]); }, [search, selectedTag]);
const detailVisibleFunctions = useMemo(() => { const detailVisibleFunctions = useMemo(() => {
return searchVisibleFunctions.filter((x) => { return searchVisibleFunctions.filter((x) => {
if (referenceTag === null || referenceTag === 'all') { if (selectedTag === null) {
if (search) { if (search) {
return true; return true;
} }
@@ -99,10 +87,19 @@ export const Reference = memo(function Reference() {
return true; return true;
} }
}); });
}, [searchVisibleFunctions, selectedFunction, referenceTag]); }, [searchVisibleFunctions, selectedFunction, selectedTag]);
const tagCounts = {};
for (const doc of availableFunctions) {
(doc.tags || ['untagged']).forEach((t) => {
if (typeof t === 'string' && t) {
tagCounts[t] = (tagCounts[t] || 0) + 1;
}
});
}
const onSearchTagFilterClick = () => { const onSearchTagFilterClick = () => {
setReferenceTag('all'); setSelectedTag(null);
setSelectedFunction(null); setSelectedFunction(null);
}; };
@@ -114,44 +111,36 @@ export const Reference = memo(function Reference() {
} }
}, [selectedFunction]); }, [selectedFunction]);
let setReferenceTag = (value) => settingsMap.setKey('referenceTag', value);
return ( return (
<div className="flex h-full w-full overflow-hidden"> <div className="flex h-full w-full p-2 overflow-hidden">
<div className="h-full text-foreground flex flex-col w-1/3 border-r border-muted"> <div className="h-full text-foreground flex flex-col gap-3 w-1/3 ">
{/* bg-background */}
<div className="w-full flex"> <div className="w-full flex">
<Textbox <Textbox
className="w-full border-0 border-b border-muted" className="w-full"
placeholder="Search..." placeholder="Search"
value={search} value={search}
onChange={(e) => { onChange={(e) => {
setSelectedFunction(null); setSelectedFunction(null);
setReferenceTag('all');
setSearch(e); setSearch(e);
}} }}
/> />
</div> </div>
{selectedTag && (
{/* <div className="flex shrink-0 flex-wrap w-full overflow-auto border-y border-muted">
<ButtonGroup wrap value={referenceTag} onChange={setReferenceTag} items={tagOptions}></ButtonGroup>
</div> */}
{referenceTag && referenceTag !== 'all' && (
<div className="w-72"> <div className="w-72">
<span <span
className="text-foreground border border-muted border-t-0 border-l-0 px-1 py-0.5 my-2 cursor-pointer font-sans" className="text-foreground border-2 border-gray-500 px-1 py-0.5 my-2 rounded-md cursor-pointer font-sans"
onClick={onSearchTagFilterClick} onClick={onSearchTagFilterClick}
> >
{referenceTag} {selectedTag}
</span> </span>
</div> </div>
)} )}
<div className="h-full p-2 overflow-y-auto bg-opacity-50"> <div className="flex flex-col h-full overflow-y-auto gap-1.5 bg-background bg-opacity-50 rounded-md">
{searchVisibleFunctions.map((entry, i) => ( {searchVisibleFunctions.map((entry, i) => (
<Fragment key={`entry-${entry.name}`}> <Fragment key={`entry-${entry.name}`}>
<a <a
className={ className={
'cursor-pointer hover:opacity-50 text-ellipsis block' + 'cursor-pointer flex-none hover:bg-lineHighlight overflow-x-hidden px-1 text-ellipsis ' +
(entry.name === selectedFunction ? 'bg-lineHighlight font-bold' : '') (entry.name === selectedFunction ? 'bg-lineHighlight font-bold' : '')
} }
onClick={() => { onClick={() => {
@@ -169,25 +158,24 @@ export const Reference = memo(function Reference() {
</div> </div>
</div> </div>
<div <div
className="w-2/3 break-normal flex-col overflow-y-auto overflow-x-hidden p-2 flex relative" className="break-normal flex-col overflow-y-auto overflow-x-hidden p-2 flex relative"
id="reference-container" id="reference-container"
> >
<div className="prose dark:prose-invert min-w-full px-1 text-sm"> <div className="prose dark:prose-invert min-w-full px-1 ">
<h2>API Reference</h2> <h2>API Reference</h2>
<p className="font-sans text-md"> <p className="font-sans text-md">
This is the long list of functions you can use. Remember that you don't need to remember all of those and This is the long list of functions you can use. Remember that you don't need to remember all of those and
that you can already make music with a small set of functions! that you can already make music with a small set of functions!
</p> </p>
<div> <div>
{/* <ButtonGroup wrap value={referenceTag} onChange={setReferenceTag} items={tagOptions}/>*/}
{Object.entries(tagCounts) {Object.entries(tagCounts)
.sort(([a], [b]) => a.localeCompare(b)) .sort(([a], [b]) => a.localeCompare(b))
.map(([t, count]) => ( .map(([t, count]) => (
<span key={t}> <span key={t}>
<a <a
className={[ className={[
'select-none text-white border border-muted px-1 py-0.5 my-2 cursor-pointer text-sm/8 no-underline font-sans', 'select-none text-white border-2 border-gray-500 px-1 py-0.5 my-2 cursor-pointer text-sm/8 rounded-md no-underline font-sans',
`${referenceTag === t ? 'bg-muted text-foreground' : ''}`, `${selectedTag === t ? 'bg-gray-500 text-black' : ''}`,
].join(' ')} ].join(' ')}
onClick={() => toggleTag(t)} onClick={() => toggleTag(t)}
> >
@@ -199,11 +187,11 @@ export const Reference = memo(function Reference() {
{detailVisibleFunctions.map((entry, i) => ( {detailVisibleFunctions.map((entry, i) => (
<section key={i} className="font-sans"> <section key={i} className="font-sans">
<div className="flex flex-row items-center mt-8 justify-between"> <div className="flex flex-row items-center mt-8 justify-between">
<h3 className="font-mono my-0 pt-4" id={`doc-${entry.name}`}> <h3 className="font-mono my-0" id={`doc-${entry.name}`}>
{entry.name} {entry.name}
</h3> </h3>
{entry.tags && ( {entry.tags && (
<span className="ml-2 text-xs text-foreground border border-muted px-1 py-0.5"> <span className="ml-2 text-xs text-gray-400 border-2 border-gray-500 rounded-md px-1 py-0.5">
{entry.tags.join(', ')} {entry.tags.join(', ')}
</span> </span>
)} )}
@@ -213,7 +201,7 @@ export const Reference = memo(function Reference() {
Synonyms: <code>{entry.synonyms_text}</code> Synonyms: <code>{entry.synonyms_text}</code>
</p> </p>
)} )}
{/* <small>{entry.meta.filename}</small> */}
<p dangerouslySetInnerHTML={{ __html: entry.description }}></p> <p dangerouslySetInnerHTML={{ __html: entry.description }}></p>
<ul> <ul>
{entry.params?.map(({ name, type, description }, i) => ( {entry.params?.map(({ name, type, description }, i) => (
@@ -223,12 +211,12 @@ export const Reference = memo(function Reference() {
))} ))}
</ul> </ul>
{entry.examples?.map((example, j) => ( {entry.examples?.map((example, j) => (
<pre className="bg-background border border-muted" key={j}> <pre className="bg-background" key={j}>
{example} {example}
</pre> </pre>
))} ))}
</section> </section>
)) || <p className="font-sans">Search or select a tag to get started.</p>} )) || <p className="font-sans">Searcb or select a tag to get started.</p>}
{detailVisibleFunctions.length > 0 && <div className="h-screen" />} {detailVisibleFunctions.length > 0 && <div className="h-screen" />}
</div> </div>
</div> </div>
@@ -0,0 +1,20 @@
import React from 'react';
// value: ?ID, options: Map<ID, any>, onChange: ID => null, onClick: event => void, isDisabled: boolean
export function SelectInput({ value, options, onChange, onClick, isDisabled }) {
return (
<select
disabled={isDisabled}
onClick={onClick}
className="p-2 bg-background rounded-md text-foreground border-foreground"
value={value ?? ''}
onChange={(e) => onChange(e.target.value)}
>
{options.size == 0 && <option value={value}>{`${value ?? 'select an option'}`}</option>}
{Array.from(options.keys()).map((id) => (
<option key={id} className="bg-background" value={id}>
{options.get(id)}
</option>
))}
</select>
);
}
@@ -1,5 +1,6 @@
import { defaultSettings, settingsMap, useSettings } from '../../../settings.mjs'; import { defaultSettings, settingsMap, useSettings } from '../../../settings.mjs';
import { themes } from '@strudel/codemirror'; import { themes } from '@strudel/codemirror';
import { Textbox } from '../textbox/Textbox.jsx';
import { confirmAndReloadPage, isUdels } from '../../util.mjs'; import { confirmAndReloadPage, isUdels } from '../../util.mjs';
import { ButtonGroup } from './Forms.jsx'; import { ButtonGroup } from './Forms.jsx';
import { AudioDeviceSelector } from './AudioDeviceSelector.jsx'; import { AudioDeviceSelector } from './AudioDeviceSelector.jsx';
@@ -9,60 +10,22 @@ import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@
import { SpecialActionButton } from '../button/action-button.jsx'; import { SpecialActionButton } from '../button/action-button.jsx';
import { ImportPrebakeScriptButton } from './ImportPrebakeScriptButton.jsx'; import { ImportPrebakeScriptButton } from './ImportPrebakeScriptButton.jsx';
function cx(...classes) {
// : Array<string | undefined>
return classes.filter(Boolean).join(' ');
}
const inputClass =
'bg-background text-xs h-8 max-h-8 border border-box rounded-0 text-foreground border-muted placeholder-muted focus:outline-none focus:ring-0 focus:border-foreground';
export function Textbox({ onChange, className, ...inputProps }) {
return (
<input className={cx('px-2', inputClass, className)} onChange={(e) => onChange(e.target.value)} {...inputProps} />
);
}
function Checkbox({ label, value, onChange, disabled = false }) { function Checkbox({ label, value, onChange, disabled = false }) {
return ( return (
<label className="text-xs"> <label>
<input <input disabled={disabled} type="checkbox" checked={value} onChange={onChange} />
className={cx(
'bg-background text-sm border border-muted focus:outline-none focus:ring-0 focus:border-foreground',
)}
disabled={disabled}
type="checkbox"
checked={value}
onChange={onChange}
/>
{' ' + label} {' ' + label}
</label> </label>
); );
} }
// value: ?ID, options: Map<ID, any>, onChange: ID => null, onClick: event => void, isDisabled: boolean
export function SelectInputDuplicate({ value, options, onChange, onClick, isDisabled }) {
return (
<select
disabled={isDisabled}
onClick={onClick}
className={cx('p-2', inputClass)}
value={value ?? ''}
onChange={(e) => onChange(e.target.value)}
>
{options.size == 0 && <option value={value}>{`${value ?? 'select an option'}`}</option>}
{Array.from(options.keys()).map((id) => (
<option key={id} className="bg-background" value={id}>
{options.get(id)}
</option>
))}
</select>
);
}
function SelectInput({ value, options, onChange }) { function SelectInput({ value, options, onChange }) {
return ( return (
<select className={cx('p-2', inputClass)} value={value} onChange={(e) => onChange(e.target.value)}> <select
className="p-2 bg-background rounded-md text-foreground border-foreground"
value={value}
onChange={(e) => onChange(e.target.value)}
>
{Object.entries(options).map(([k, label]) => ( {Object.entries(options).map(([k, label]) => (
<option key={k} className="bg-background" value={k}> <option key={k} className="bg-background" value={k}>
{label} {label}
@@ -74,9 +37,9 @@ function SelectInput({ value, options, onChange }) {
function NumberSlider({ value, onChange, step = 1, ...rest }) { function NumberSlider({ value, onChange, step = 1, ...rest }) {
return ( return (
<div className="flex space-x-2 gap-1 overflow-hidden"> <div className="flex space-x-2 gap-1">
<input <input
className="p-2 grow accent-foreground" className="p-2 grow"
type="range" type="range"
value={value} value={value}
step={step} step={step}
@@ -87,7 +50,7 @@ function NumberSlider({ value, onChange, step = 1, ...rest }) {
type="number" type="number"
value={value} value={value}
step={step} step={step}
className={cx('w-16', inputClass)} className="w-16 bg-background rounded-md"
onChange={(e) => onChange(Number(e.target.value))} onChange={(e) => onChange(Number(e.target.value))}
/> />
</div> </div>
@@ -96,8 +59,8 @@ function NumberSlider({ value, onChange, step = 1, ...rest }) {
function FormItem({ label, children, sublabel }) { function FormItem({ label, children, sublabel }) {
return ( return (
<div className="grid gap-2 text-xs"> <div className="grid gap-2">
<label className="text-sm">{label}</label> <label>{label}</label>
{children} {children}
</div> </div>
); );
@@ -144,6 +107,7 @@ export function SettingsTab({ started }) {
panelPosition, panelPosition,
audioDeviceName, audioDeviceName,
audioEngineTarget, audioEngineTarget,
togglePanelTrigger,
maxPolyphony, maxPolyphony,
multiChannelOrbits, multiChannelOrbits,
isTabIndentationEnabled, isTabIndentationEnabled,
@@ -154,7 +118,7 @@ export function SettingsTab({ started }) {
const shouldAlwaysSync = isUdels(); const shouldAlwaysSync = isUdels();
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null; const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
return ( return (
<div className="p-4 text-foreground space-y-4 w-full" style={{ fontFamily }}> <div className="text-foreground p-4 space-y-4 w-full" style={{ fontFamily }}>
{canChangeAudioDevice && ( {canChangeAudioDevice && (
<FormItem label="Audio Output Device"> <FormItem label="Audio Output Device">
<AudioDeviceSelector <AudioDeviceSelector
@@ -214,7 +178,7 @@ export function SettingsTab({ started }) {
<FormItem label="Theme"> <FormItem label="Theme">
<SelectInput options={themeOptions} value={theme} onChange={(theme) => settingsMap.setKey('theme', theme)} /> <SelectInput options={themeOptions} value={theme} onChange={(theme) => settingsMap.setKey('theme', theme)} />
</FormItem> </FormItem>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 font-sans">
<FormItem label="Font Family"> <FormItem label="Font Family">
<SelectInput <SelectInput
options={fontFamilyOptions} options={fontFamilyOptions}
@@ -255,6 +219,13 @@ export function SettingsTab({ started }) {
items={{ bottom: 'Bottom', right: 'Right' }} items={{ bottom: 'Bottom', right: 'Right' }}
></ButtonGroup> ></ButtonGroup>
</FormItem> </FormItem>
<FormItem label="Open Panel on: ">
<ButtonGroup
value={togglePanelTrigger}
onChange={(value) => settingsMap.setKey('togglePanelTrigger', value)}
items={{ click: 'Click', hover: 'Hover' }}
/>
</FormItem>
<FormItem label="More Settings"> <FormItem label="More Settings">
<Checkbox <Checkbox
label="Enable bracket matching" label="Enable bracket matching"
@@ -323,7 +294,7 @@ export function SettingsTab({ started }) {
value={isSyncEnabled} value={isSyncEnabled}
/> />
<Checkbox <Checkbox
label="Hide action buttons" label="Hide top buttons"
onChange={(cbEvent) => settingsMap.setKey('isButtonRowHidden', cbEvent.target.checked)} onChange={(cbEvent) => settingsMap.setKey('isButtonRowHidden', cbEvent.target.checked)}
value={isButtonRowHidden} value={isButtonRowHidden}
/> />
@@ -5,7 +5,7 @@ import { useMemo, useRef, useState } from 'react';
import { settingsMap, soundFilterType, useSettings } from '../../../settings.mjs'; import { settingsMap, soundFilterType, useSettings } from '../../../settings.mjs';
import { ButtonGroup } from './Forms.jsx'; import { ButtonGroup } from './Forms.jsx';
import ImportSoundsButton from './ImportSoundsButton.jsx'; import ImportSoundsButton from './ImportSoundsButton.jsx';
import { Textbox } from '@src/repl/components/panel/SettingsTab.jsx'; import { Textbox } from '../textbox/Textbox.jsx';
import { ActionButton } from '../button/action-button.jsx'; import { ActionButton } from '../button/action-button.jsx';
import { confirmDialog } from '@src/repl/util.mjs'; import { confirmDialog } from '@src/repl/util.mjs';
import { clearIDB, userSamplesDBConfig } from '@src/repl/idbutils.mjs'; import { clearIDB, userSamplesDBConfig } from '@src/repl/idbutils.mjs';
@@ -77,12 +77,11 @@ export function SoundsTab() {
numRef.current = 0; numRef.current = 0;
}); });
return ( return (
<div id="sounds-tab" className="flex flex-col w-full h-full text-foreground"> <div id="sounds-tab" className="px-4 flex gap-2 flex-col w-full h-full text-foreground">
<Textbox placeholder="Search..." className="border-0" value={search} onChange={(v) => setSearch(v)} /> <Textbox placeholder="Search" value={search} onChange={(v) => setSearch(v)} />
<div className="flex shrink-0 flex-wrap border-y border-muted"> <div className=" flex shrink-0 flex-wrap">
<ButtonGroup <ButtonGroup
wrap
value={soundsFilter} value={soundsFilter}
onChange={(value) => settingsMap.setKey('soundsFilter', value)} onChange={(value) => settingsMap.setKey('soundsFilter', value)}
items={{ items={{
@@ -115,7 +114,7 @@ export function SoundsTab() {
/> />
)} )}
<div className="min-h-0 max-h-full grow overflow-auto break-normal p-2"> <div className="min-h-0 max-h-full grow overflow-auto text-sm break-normal bg-background p-2 rounded-md">
{soundEntries.map(([name, { data, onTrigger }]) => { {soundEntries.map(([name, { data, onTrigger }]) => {
return ( return (
<span <span
@@ -166,7 +165,7 @@ export function SoundsTab() {
); );
})} })}
{!soundEntries.length && soundsFilter === 'importSounds' ? ( {!soundEntries.length && soundsFilter === 'importSounds' ? (
<div className="prose dark:prose-invert min-w-full text-sm"> <div className="prose dark:prose-invert min-w-full pt-2 pb-8 px-4">
<ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} /> <ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} />
<p> <p>
To import sounds into strudel, they must be contained{' '} To import sounds into strudel, they must be contained{' '}
@@ -6,8 +6,8 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
export function WelcomeTab({ context }) { export function WelcomeTab({ context }) {
const { fontFamily } = useSettings(); const { fontFamily } = useSettings();
return ( return (
<div className="prose dark:prose-invert min-w-full py-4 font-sans px-4 text-sm" style={{ fontFamily }}> <div className="prose dark:prose-invert min-w-full pt-2 font-sans pb-8 px-4 " style={{ fontFamily }}>
<h3>welcome</h3> <h3> welcome</h3>
<p> <p>
You have found <span className="underline">strudel</span>, a new live coding platform to write dynamic music You have found <span className="underline">strudel</span>, a new live coding platform to write dynamic music
pieces in the browser! It is free and open-source and made for beginners and experts alike. To get started: pieces in the browser! It is free and open-source and made for beginners and experts alike. To get started:
@@ -30,7 +30,7 @@ export function WelcomeTab({ context }) {
</a>{' '} </a>{' '}
to ask any questions, give feedback or just say hello. to ask any questions, give feedback or just say hello.
</p> </p>
<h3>about</h3> <h3> about</h3>
<p> <p>
strudel is a JavaScript version of{' '} strudel is a JavaScript version of{' '}
<a href="https://tidalcycles.org/" target="_blank"> <a href="https://tidalcycles.org/" target="_blank">
@@ -0,0 +1,14 @@
import cx from '@src/cx.mjs';
export function Textbox({ onChange, className, ...inputProps }) {
return (
<input
className={cx(
'p-2 bg-background rounded-md border-foreground text-foreground placeholder-foreground',
className,
)}
onChange={(e) => onChange(e.target.value)}
{...inputProps}
/>
);
}
+1 -1
View File
@@ -21,7 +21,7 @@ function getUpdatedLog(log, event) {
} else { } else {
log = log.concat([{ message, type, id, data }]); log = log.concat([{ message, type, id, data }]);
} }
return log.slice(-40); return log.slice(-20);
} }
export function useLogger() { export function useLogger() {
+6 -9
View File
@@ -2,7 +2,6 @@ import { persistentMap } from '@nanostores/persistent';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { register } from '@strudel/core'; import { register } from '@strudel/core';
import { isUdels } from './repl/util.mjs'; import { isUdels } from './repl/util.mjs';
import { computed } from 'nanostores';
export const audioEngineTargets = { export const audioEngineTargets = {
webaudio: 'webaudio', webaudio: 'webaudio',
@@ -39,12 +38,12 @@ export const defaultSettings = {
latestCode: '', latestCode: '',
isZen: false, isZen: false,
soundsFilter: soundFilterType.ALL, soundsFilter: soundFilterType.ALL,
referenceTag: 'all',
patternFilter: 'community', patternFilter: 'community',
// panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom', //FIX: does not work on astro // panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom', //FIX: does not work on astro
panelPosition: 'right', panelPosition: 'right',
isPanelPinned: false, isPanelPinned: false,
isPanelOpen: true, isPanelOpen: true,
togglePanelTrigger: 'click', //click | hover
userPatterns: '{}', userPatterns: '{}',
prebakeScript: '', prebakeScript: '',
audioEngineTarget: audioEngineTargets.webaudio, audioEngineTarget: audioEngineTargets.webaudio,
@@ -65,7 +64,11 @@ const settings_key = `strudel-settings${instance > 0 ? instance : ''}`;
export const settingsMap = persistentMap(settings_key, defaultSettings); export const settingsMap = persistentMap(settings_key, defaultSettings);
export const $settings = computed(settingsMap, (state) => { export const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false);
export function useSettings() {
const state = useStore(settingsMap);
const userPatterns = JSON.parse(state.userPatterns); const userPatterns = JSON.parse(state.userPatterns);
Object.keys(userPatterns).forEach((key) => { Object.keys(userPatterns).forEach((key) => {
const data = userPatterns[key]; const data = userPatterns[key];
@@ -102,12 +105,6 @@ export const $settings = computed(settingsMap, (state) => {
? true ? true
: parseBoolean(state.patternAutoStart), : parseBoolean(state.patternAutoStart),
}; };
});
export const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false);
export function useSettings() {
return useStore($settings);
} }
export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab); export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
+7 -4
View File
@@ -1,4 +1,4 @@
import { atom, computed } from 'nanostores'; import { atom } from 'nanostores';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { logger } from '@strudel/core'; import { logger } from '@strudel/core';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
@@ -36,9 +36,12 @@ export let $viewingPatternData = sessionAtom('viewingPatternData', {
created_at: Date.now(), created_at: Date.now(),
}); });
const $viewingPatterns = computed($viewingPatternData, (state) => parseJSON(state)); export const getViewingPatternData = () => {
export const useViewingPatternData = () => useStore($viewingPatterns); return parseJSON($viewingPatternData.get());
export const getViewingPatternData = () => $viewingPatterns.get(); };
export const useViewingPatternData = () => {
return useStore($viewingPatternData);
};
export const setViewingPatternData = (data) => { export const setViewingPatternData = (data) => {
$viewingPatternData.set(JSON.stringify(data)); $viewingPatternData.set(JSON.stringify(data));
+9 -10
View File
@@ -24,7 +24,6 @@ module.exports = {
background: 'var(--background)', background: 'var(--background)',
lineBackground: 'var(--lineBackground)', lineBackground: 'var(--lineBackground)',
foreground: 'var(--foreground)', foreground: 'var(--foreground)',
muted: 'var(--muted)',
caret: 'var(--caret)', caret: 'var(--caret)',
selection: 'var(--selection)', selection: 'var(--selection)',
selectionMatch: 'var(--selectionMatch)', selectionMatch: 'var(--selectionMatch)',
@@ -47,28 +46,28 @@ module.exports = {
'code::after': { 'code::after': {
content: 'none', content: 'none',
}, },
color: 'var(--foreground) !important', color: 'var(--foreground)',
a: { a: {
color: 'var(--foreground) !important', color: 'var(--foreground)',
}, },
h1: { h1: {
color: 'var(--foreground) !important', color: 'var(--foreground)',
}, },
h2: { h2: {
color: 'var(--foreground) !important', color: 'var(--foreground)',
}, },
h3: { h3: {
color: 'var(--foreground) !important', color: 'var(--foreground)',
}, },
h4: { h4: {
color: 'var(--foreground) !important', color: 'var(--foreground)',
}, },
pre: { pre: {
color: 'var(--foreground) !important', color: 'var(--foreground)',
background: 'var(--background) !important', background: 'var(--background)',
}, },
code: { code: {
color: 'var(--foreground) !important', color: 'var(--foreground)',
}, },
}, },
}, },