Merge remote-tracking branch 'origin/main' into fix-array-args
This commit is contained in:
commit
49234183a3
45 changed files with 1605 additions and 482 deletions
|
|
@ -126,6 +126,19 @@ export class Hap {
|
||||||
setContext(context) {
|
setContext(context) {
|
||||||
return new Hap(this.whole, this.part, this.value, context);
|
return new Hap(this.whole, this.part, this.value, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureObjectValue() {
|
||||||
|
/* if (isNote(hap.value)) {
|
||||||
|
// supports primitive hap values that look like notes
|
||||||
|
hap.value = { note: hap.value };
|
||||||
|
} */
|
||||||
|
if (typeof this.value !== 'object') {
|
||||||
|
throw new Error(
|
||||||
|
`expected hap.value to be an object, but got "${this.value}". Hint: append .note() or .s() to the end`,
|
||||||
|
'error',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Hap;
|
export default Hap;
|
||||||
|
|
|
||||||
|
|
@ -1595,11 +1595,11 @@ export const trigzero = methodToFunction('trigzero');
|
||||||
* @noAutocomplete
|
* @noAutocomplete
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export function register(name, func) {
|
export function register(name, func, patternify = true) {
|
||||||
if (Array.isArray(name)) {
|
if (Array.isArray(name)) {
|
||||||
const result = {};
|
const result = {};
|
||||||
for (const name_item of name) {
|
for (const name_item of name) {
|
||||||
result[name_item] = register(name_item, func);
|
result[name_item] = register(name_item, func, patternify);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -1607,29 +1607,40 @@ export function register(name, func) {
|
||||||
|
|
||||||
registerMethod(name);
|
registerMethod(name);
|
||||||
|
|
||||||
const pfunc = function (...args) {
|
var pfunc;
|
||||||
args = args.map(reify);
|
|
||||||
const pat = args[args.length - 1];
|
if (patternify) {
|
||||||
if (arity === 1) {
|
pfunc = function (...args) {
|
||||||
return func(pat);
|
args = args.map(reify);
|
||||||
}
|
const pat = args[args.length - 1];
|
||||||
const [left, ...right] = args.slice(0, -1);
|
if (arity === 1) {
|
||||||
let mapFn = (...args) => {
|
return func(pat);
|
||||||
// make sure to call func with the correct argument count
|
}
|
||||||
// args.length is expected to be <= arity-1
|
const [left, ...right] = args.slice(0, -1);
|
||||||
// so we set undefined args explicitly undefined
|
let mapFn = (...args) => {
|
||||||
Array(arity - 1)
|
// make sure to call func with the correct argument count
|
||||||
.fill()
|
// args.length is expected to be <= arity-1
|
||||||
.map((_, i) => args[i] ?? undefined);
|
// so we set undefined args explicitly undefined
|
||||||
return func(...args, pat);
|
Array(arity - 1)
|
||||||
|
.fill()
|
||||||
|
.map((_, i) => args[i] ?? undefined);
|
||||||
|
return func(...args, pat);
|
||||||
|
};
|
||||||
|
mapFn = curryPattern(mapFn, arity - 1);
|
||||||
|
|
||||||
|
const app = function (acc, p, i) {
|
||||||
|
return acc.appLeft(p);
|
||||||
|
};
|
||||||
|
const start = left.fmap(mapFn);
|
||||||
|
|
||||||
|
return right.reduce(app, start).innerJoin();
|
||||||
};
|
};
|
||||||
mapFn = curryPattern(mapFn, arity - 1);
|
} else {
|
||||||
|
pfunc = function (...args) {
|
||||||
const app = (acc, p) => acc.appLeft(p);
|
args = args.map(reify);
|
||||||
const start = left.fmap(mapFn);
|
return func(...args);
|
||||||
|
};
|
||||||
return right.reduce(app, start).innerJoin();
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Pattern.prototype[name] = function (...args) {
|
Pattern.prototype[name] = function (...args) {
|
||||||
// For methods that take a single argument (plus 'this'), allow
|
// For methods that take a single argument (plus 'this'), allow
|
||||||
|
|
@ -2419,6 +2430,59 @@ const _loopAt = function (factor, pat, cps = 1) {
|
||||||
.slow(factor);
|
.slow(factor);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chops samples into the given number of slices, triggering those slices with a given pattern of slice numbers.
|
||||||
|
* @name slice
|
||||||
|
* @memberof Pattern
|
||||||
|
* @returns Pattern
|
||||||
|
* @example
|
||||||
|
* await samples('github:tidalcycles/Dirt-Samples/master')
|
||||||
|
* s("breaks165").slice(8, "0 1 <2 2*2> 3 [4 0] 5 6 7".every(3, rev)).slow(1.5)
|
||||||
|
*/
|
||||||
|
const slice = register(
|
||||||
|
'slice',
|
||||||
|
function (npat, ipat, opat) {
|
||||||
|
return npat.innerBind((n) =>
|
||||||
|
ipat.outerBind((i) =>
|
||||||
|
opat.outerBind((o) => {
|
||||||
|
// If it's not an object, assume it's a string and make it a 's' control parameter
|
||||||
|
o = o instanceof Object ? o : { s: o };
|
||||||
|
// Remember we must stay pure and avoid editing the object directly
|
||||||
|
const toAdd = { begin: i / n, end: (i + 1) / n, _slices: n };
|
||||||
|
return pure({ ...toAdd, ...o });
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
false, // turns off auto-patternification
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Works the same as slice, but changes the playback speed of each slice to match the duration of its step.
|
||||||
|
* @name splice
|
||||||
|
* @memberof Pattern
|
||||||
|
* @returns Pattern
|
||||||
|
* @example
|
||||||
|
* await samples('github:tidalcycles/Dirt-Samples/master')
|
||||||
|
* s("breaks165").splice(8, "0 1 [2 3 0]@2 3 0@2 7").hurry(0.65)
|
||||||
|
*/
|
||||||
|
const splice = register(
|
||||||
|
'splice',
|
||||||
|
function (npat, ipat, opat) {
|
||||||
|
const sliced = slice(npat, ipat, opat);
|
||||||
|
return sliced.withHap(function (hap) {
|
||||||
|
return hap.withValue((v) => ({
|
||||||
|
...{
|
||||||
|
speed: (1 / v._slices / hap.whole.duration) * (v.speed || 1),
|
||||||
|
unit: 'c',
|
||||||
|
},
|
||||||
|
...v,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
false, // turns off auto-patternification
|
||||||
|
);
|
||||||
|
|
||||||
const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
|
const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat) {
|
||||||
return _loopAt(factor, pat, 1);
|
return _loopAt(factor, pat, 1);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ import {
|
||||||
run,
|
run,
|
||||||
hitch,
|
hitch,
|
||||||
set,
|
set,
|
||||||
|
begin,
|
||||||
} from '../index.mjs';
|
} from '../index.mjs';
|
||||||
|
|
||||||
import { steady } from '../signal.mjs';
|
import { steady } from '../signal.mjs';
|
||||||
|
|
@ -986,4 +987,30 @@ describe('Pattern', () => {
|
||||||
sameFirst(n(0, 1).weave(2, s('bd', silence), s(silence, 'sd')), sequence(s('bd').n(0), s('sd').n(1)));
|
sameFirst(n(0, 1).weave(2, s('bd', silence), s(silence, 'sd')), sequence(s('bd').n(0), s('sd').n(1)));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('slice', () => {
|
||||||
|
it('Can slice a sample', () => {
|
||||||
|
sameFirst(
|
||||||
|
s('break').slice(4, sequence(0, 1, 2, 3)),
|
||||||
|
sequence(
|
||||||
|
{ begin: 0, end: 0.25, s: 'break', _slices: 4 },
|
||||||
|
{ begin: 0.25, end: 0.5, s: 'break', _slices: 4 },
|
||||||
|
{ begin: 0.5, end: 0.75, s: 'break', _slices: 4 },
|
||||||
|
{ begin: 0.75, end: 1, s: 'break', _slices: 4 },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('splice', () => {
|
||||||
|
it('Can splice a sample', () => {
|
||||||
|
sameFirst(
|
||||||
|
s('break').splice(4, sequence(0, 1, 2, 3)),
|
||||||
|
sequence(
|
||||||
|
{ begin: 0, end: 0.25, s: 'break', _slices: 4, unit: 'c', speed: 1 },
|
||||||
|
{ begin: 0.25, end: 0.5, s: 'break', _slices: 4, unit: 'c', speed: 1 },
|
||||||
|
{ begin: 0.5, end: 0.75, s: 'break', _slices: 4, unit: 'c', speed: 1 },
|
||||||
|
{ begin: 0.75, end: 1, s: 'break', _slices: 4, unit: 'c', speed: 1 },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,7 @@ export const csound = register('csound', (instrument, pat) => {
|
||||||
logger('[csound] not loaded yet', 'warning');
|
logger('[csound] not loaded yet', 'warning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof hap.value !== 'object') {
|
hap.ensureObjectValue();
|
||||||
throw new Error('csound only support objects as hap values');
|
|
||||||
}
|
|
||||||
let { gain = 0.8 } = hap.value;
|
let { gain = 0.8 } = hap.value;
|
||||||
gain *= 0.2;
|
gain *= 0.2;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as _WebMidi from 'webmidi';
|
import * as _WebMidi from 'webmidi';
|
||||||
import { Pattern, isPattern, isNote, getPlayableNoteValue, logger } from '@strudel.cycles/core';
|
import { Pattern, isPattern, logger } from '@strudel.cycles/core';
|
||||||
import { getAudioContext } from '@strudel.cycles/webaudio';
|
import { getAudioContext } from '@strudel.cycles/webaudio';
|
||||||
|
import { toMidi } from '@strudel.cycles/core';
|
||||||
|
|
||||||
// if you use WebMidi from outside of this package, make sure to import that instance:
|
// if you use WebMidi from outside of this package, make sure to import that instance:
|
||||||
export const { WebMidi } = _WebMidi;
|
export const { WebMidi } = _WebMidi;
|
||||||
|
|
@ -63,7 +64,7 @@ function getDevice(output, outputs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pattern.prototype.midi = function (output: string | number, channel = 1) {
|
// Pattern.prototype.midi = function (output: string | number, channel = 1) {
|
||||||
Pattern.prototype.midi = function (output, channel = 1) {
|
Pattern.prototype.midi = function (output) {
|
||||||
if (!supportsMidi()) {
|
if (!supportsMidi()) {
|
||||||
throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
|
throw new Error(`🎹 WebMidi is not enabled. Supported Browsers: https://caniuse.com/?search=webmidi`);
|
||||||
}
|
}
|
||||||
|
|
@ -90,11 +91,6 @@ Pattern.prototype.midi = function (output, channel = 1) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return this.onTrigger((time, hap) => {
|
return this.onTrigger((time, hap) => {
|
||||||
let note = getPlayableNoteValue(hap);
|
|
||||||
const velocity = hap.context?.velocity ?? 0.9;
|
|
||||||
if (!isNote(note)) {
|
|
||||||
throw new Error('not a note: ' + note);
|
|
||||||
}
|
|
||||||
if (!midiReady) {
|
if (!midiReady) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -106,15 +102,34 @@ Pattern.prototype.midi = function (output, channel = 1) {
|
||||||
.join(' | ')}`,
|
.join(' | ')}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// console.log('midi', value, output);
|
hap.ensureObjectValue();
|
||||||
|
|
||||||
|
// calculate time
|
||||||
const timingOffset = WebMidi.time - getAudioContext().currentTime * 1000;
|
const timingOffset = WebMidi.time - getAudioContext().currentTime * 1000;
|
||||||
time = time * 1000 + timingOffset;
|
time = time * 1000 + timingOffset;
|
||||||
// const inMs = '+' + (time - Tone.getContext().currentTime) * 1000;
|
|
||||||
// await enableWebMidi()
|
// destructure value
|
||||||
device.playNote(note, channel, {
|
const { note, nrpnn, nrpv, ccn, ccv, midichan = 1 } = hap.value;
|
||||||
time,
|
const velocity = hap.context?.velocity ?? 0.9; // TODO: refactor velocity
|
||||||
duration: hap.duration.valueOf() * 1000 - 5,
|
const duration = hap.duration.valueOf() * 1000 - 5;
|
||||||
attack: velocity,
|
|
||||||
});
|
if (note) {
|
||||||
|
const midiNumber = toMidi(note);
|
||||||
|
device.playNote(midiNumber, midichan, {
|
||||||
|
time,
|
||||||
|
duration,
|
||||||
|
attack: velocity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (ccv && ccn) {
|
||||||
|
if (typeof ccv !== 'number' || ccv < 0 || ccv > 1) {
|
||||||
|
throw new Error('expected ccv to be a number between 0 and 1');
|
||||||
|
}
|
||||||
|
if (!['string', 'number'].includes(typeof ccn)) {
|
||||||
|
throw new Error('expected ccn to be a number or a string');
|
||||||
|
}
|
||||||
|
const scaled = Math.round(ccv * 127);
|
||||||
|
device.sendControlChange(ccn, scaled, midichan, { time });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,10 @@ let startedAt = -1;
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
*/
|
*/
|
||||||
Pattern.prototype.osc = async function () {
|
Pattern.prototype.osc = function () {
|
||||||
const osc = await connect();
|
return this.onTrigger(async (time, hap, currentTime, cps = 1) => {
|
||||||
return this.onTrigger((time, hap, currentTime, cps = 1) => {
|
hap.ensureObjectValue();
|
||||||
|
const osc = await connect();
|
||||||
const cycle = hap.wholeOrPart().begin.valueOf();
|
const cycle = hap.wholeOrPart().begin.valueOf();
|
||||||
const delta = hap.duration.valueOf();
|
const delta = hap.duration.valueOf();
|
||||||
// time should be audio time of onset
|
// time should be audio time of onset
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@
|
||||||
"@codemirror/state": "^6.2.0",
|
"@codemirror/state": "^6.2.0",
|
||||||
"@codemirror/view": "^6.7.3",
|
"@codemirror/view": "^6.7.3",
|
||||||
"@lezer/highlight": "^1.1.3",
|
"@lezer/highlight": "^1.1.3",
|
||||||
|
"@replit/codemirror-emacs": "^6.0.0",
|
||||||
|
"@replit/codemirror-vim": "^6.0.6",
|
||||||
"@strudel.cycles/core": "workspace:*",
|
"@strudel.cycles/core": "workspace:*",
|
||||||
"@strudel.cycles/transpiler": "workspace:*",
|
"@strudel.cycles/transpiler": "workspace:*",
|
||||||
"@strudel.cycles/webaudio": "workspace:*",
|
"@strudel.cycles/webaudio": "workspace:*",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import _CodeMirror from '@uiw/react-codemirror';
|
import _CodeMirror from '@uiw/react-codemirror';
|
||||||
import { EditorView, Decoration } from '@codemirror/view';
|
import { EditorView, Decoration } from '@codemirror/view';
|
||||||
import { StateField, StateEffect } from '@codemirror/state';
|
import { StateField, StateEffect } from '@codemirror/state';
|
||||||
|
|
@ -8,6 +8,8 @@ import './style.css';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { autocompletion } from '@codemirror/autocomplete';
|
import { autocompletion } from '@codemirror/autocomplete';
|
||||||
import { strudelAutocomplete } from './Autocomplete';
|
import { strudelAutocomplete } from './Autocomplete';
|
||||||
|
import { vim } from '@replit/codemirror-vim';
|
||||||
|
import { emacs } from '@replit/codemirror-emacs';
|
||||||
|
|
||||||
export const setFlash = StateEffect.define();
|
export const setFlash = StateEffect.define();
|
||||||
const flashField = StateField.define({
|
const flashField = StateField.define({
|
||||||
|
|
@ -56,15 +58,15 @@ const highlightField = StateField.define({
|
||||||
haps
|
haps
|
||||||
.map((hap) =>
|
.map((hap) =>
|
||||||
(hap.context.locations || []).map(({ start, end }) => {
|
(hap.context.locations || []).map(({ start, end }) => {
|
||||||
const color = hap.context.color || e.value.color || '#FFCA28';
|
// const color = hap.context.color || e.value.color || '#FFCA28';
|
||||||
let from = tr.newDoc.line(start.line).from + start.column;
|
let from = tr.newDoc.line(start.line).from + start.column;
|
||||||
let to = tr.newDoc.line(end.line).from + end.column;
|
let to = tr.newDoc.line(end.line).from + end.column;
|
||||||
const l = tr.newDoc.length;
|
const l = tr.newDoc.length;
|
||||||
if (from > l || to > l) {
|
if (from > l || to > l) {
|
||||||
return; // dont mark outside of range, as it will throw an error
|
return; // dont mark outside of range, as it will throw an error
|
||||||
}
|
}
|
||||||
// const mark = Decoration.mark({ attributes: { style: `outline: 1px solid ${color}` } });
|
//const mark = Decoration.mark({ attributes: { style: `outline: 2px solid ${color};` } });
|
||||||
const mark = Decoration.mark({ attributes: { style: `outline: 1.5px solid ${color};` } });
|
const mark = Decoration.mark({ attributes: { class: `outline outline-2 outline-foreground` } });
|
||||||
return mark.range(from, to);
|
return mark.range(from, to);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
@ -82,7 +84,7 @@ const highlightField = StateField.define({
|
||||||
provide: (f) => EditorView.decorations.from(f),
|
provide: (f) => EditorView.decorations.from(f),
|
||||||
});
|
});
|
||||||
|
|
||||||
const extensions = [
|
const staticExtensions = [
|
||||||
javascript(),
|
javascript(),
|
||||||
highlightField,
|
highlightField,
|
||||||
flashField,
|
flashField,
|
||||||
|
|
@ -97,6 +99,9 @@ export default function CodeMirror({
|
||||||
onViewChanged,
|
onViewChanged,
|
||||||
onSelectionChange,
|
onSelectionChange,
|
||||||
theme,
|
theme,
|
||||||
|
keybindings,
|
||||||
|
fontSize = 18,
|
||||||
|
fontFamily = 'monospace',
|
||||||
options,
|
options,
|
||||||
editorDidMount,
|
editorDidMount,
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -120,8 +125,18 @@ export default function CodeMirror({
|
||||||
},
|
},
|
||||||
[onSelectionChange],
|
[onSelectionChange],
|
||||||
);
|
);
|
||||||
|
const extensions = useMemo(() => {
|
||||||
|
let bindings = {
|
||||||
|
vim,
|
||||||
|
emacs,
|
||||||
|
};
|
||||||
|
if (bindings[keybindings]) {
|
||||||
|
return [...staticExtensions, bindings[keybindings]()];
|
||||||
|
}
|
||||||
|
return staticExtensions;
|
||||||
|
}, [keybindings]);
|
||||||
return (
|
return (
|
||||||
<>
|
<div style={{ fontSize, fontFamily }} className="w-full">
|
||||||
<_CodeMirror
|
<_CodeMirror
|
||||||
value={value}
|
value={value}
|
||||||
theme={theme || strudelTheme}
|
theme={theme || strudelTheme}
|
||||||
|
|
@ -130,7 +145,7 @@ export default function CodeMirror({
|
||||||
onUpdate={handleOnUpdate}
|
onUpdate={handleOnUpdate}
|
||||||
extensions={extensions}
|
extensions={extensions}
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ export function MiniRepl({
|
||||||
punchcard,
|
punchcard,
|
||||||
canvasHeight = 200,
|
canvasHeight = 200,
|
||||||
theme,
|
theme,
|
||||||
highlightColor,
|
|
||||||
}) {
|
}) {
|
||||||
drawTime = drawTime || (punchcard ? [0, 4] : undefined);
|
drawTime = drawTime || (punchcard ? [0, 4] : undefined);
|
||||||
const evalOnMount = !!drawTime;
|
const evalOnMount = !!drawTime;
|
||||||
|
|
@ -72,7 +71,6 @@ export function MiniRepl({
|
||||||
pattern,
|
pattern,
|
||||||
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
||||||
getTime: () => scheduler.now(),
|
getTime: () => scheduler.now(),
|
||||||
color: highlightColor,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// keyboard shortcuts
|
// keyboard shortcuts
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
font-size: 18px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-theme {
|
.cm-theme {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-theme-light {
|
.cm-theme-light {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { setHighlights } from '../components/CodeMirror6';
|
import { setHighlights } from '../components/CodeMirror6';
|
||||||
|
|
||||||
function useHighlighting({ view, pattern, active, getTime, color }) {
|
function useHighlighting({ view, pattern, active, getTime }) {
|
||||||
const highlights = useRef([]);
|
const highlights = useRef([]);
|
||||||
const lastEnd = useRef(0);
|
const lastEnd = useRef(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -19,7 +19,7 @@ function useHighlighting({ view, pattern, active, getTime, color }) {
|
||||||
highlights.current = highlights.current.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active
|
highlights.current = highlights.current.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active
|
||||||
const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset());
|
const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset());
|
||||||
highlights.current = highlights.current.concat(haps); // add potential new onsets
|
highlights.current = highlights.current.concat(haps); // add potential new onsets
|
||||||
view.dispatch({ effects: setHighlights.of({ haps: highlights.current, color }) }); // highlight all still active + new active haps
|
view.dispatch({ effects: setHighlights.of({ haps: highlights.current }) }); // highlight all still active + new active haps
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
view.dispatch({ effects: setHighlights.of({ haps: [] }) });
|
view.dispatch({ effects: setHighlights.of({ haps: [] }) });
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ function useHighlighting({ view, pattern, active, getTime, color }) {
|
||||||
view.dispatch({ effects: setHighlights.of({ haps: [] }) });
|
view.dispatch({ effects: setHighlights.of({ haps: [] }) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [pattern, active, view, color]);
|
}, [pattern, active, view]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useHighlighting;
|
export default useHighlighting;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ function useStrudel({
|
||||||
getTime,
|
getTime,
|
||||||
evalOnMount = false,
|
evalOnMount = false,
|
||||||
initialCode = '',
|
initialCode = '',
|
||||||
autolink = false,
|
|
||||||
beforeEval,
|
beforeEval,
|
||||||
afterEval,
|
afterEval,
|
||||||
editPattern,
|
editPattern,
|
||||||
|
|
@ -51,15 +50,13 @@ function useStrudel({
|
||||||
setCode(code);
|
setCode(code);
|
||||||
beforeEval?.();
|
beforeEval?.();
|
||||||
},
|
},
|
||||||
afterEval: ({ pattern: _pattern, code }) => {
|
afterEval: (res) => {
|
||||||
|
const { pattern: _pattern, code } = res;
|
||||||
setActiveCode(code);
|
setActiveCode(code);
|
||||||
setPattern(_pattern);
|
setPattern(_pattern);
|
||||||
setEvalError();
|
setEvalError();
|
||||||
setSchedulerError();
|
setSchedulerError();
|
||||||
if (autolink) {
|
afterEval?.(res);
|
||||||
window.location.hash = '#' + encodeURIComponent(btoa(code));
|
|
||||||
}
|
|
||||||
afterEval?.();
|
|
||||||
},
|
},
|
||||||
onToggle: (v) => {
|
onToggle: (v) => {
|
||||||
setStarted(v);
|
setStarted(v);
|
||||||
|
|
|
||||||
39
packages/react/src/themes/algoboy.js
Normal file
39
packages/react/src/themes/algoboy.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
|
export const settings = {
|
||||||
|
background: '#9bbc0f',
|
||||||
|
foreground: '#0f380f', // whats that?
|
||||||
|
caret: '#0f380f',
|
||||||
|
selection: '#306230',
|
||||||
|
selectionMatch: '#ffffff26',
|
||||||
|
lineHighlight: '#8bac0f',
|
||||||
|
lineBackground: '#9bbc0f50',
|
||||||
|
//lineBackground: 'transparent',
|
||||||
|
gutterBackground: 'transparent',
|
||||||
|
gutterForeground: '#0f380f',
|
||||||
|
light: true,
|
||||||
|
};
|
||||||
|
export default createTheme({
|
||||||
|
theme: 'light',
|
||||||
|
settings,
|
||||||
|
styles: [
|
||||||
|
{ tag: t.keyword, color: '#0f380f' },
|
||||||
|
{ tag: t.operator, color: '#0f380f' },
|
||||||
|
{ tag: t.special(t.variableName), color: '#0f380f' },
|
||||||
|
{ tag: t.typeName, color: '#0f380f' },
|
||||||
|
{ tag: t.atom, color: '#0f380f' },
|
||||||
|
{ tag: t.number, color: '#0f380f' },
|
||||||
|
{ tag: t.definition(t.variableName), color: '#0f380f' },
|
||||||
|
{ tag: t.string, color: '#0f380f' },
|
||||||
|
{ tag: t.special(t.string), color: '#0f380f' },
|
||||||
|
{ tag: t.comment, color: '#0f380f' },
|
||||||
|
{ tag: t.variableName, color: '#0f380f' },
|
||||||
|
{ tag: t.tagName, color: '#0f380f' },
|
||||||
|
{ tag: t.bracket, color: '#0f380f' },
|
||||||
|
{ tag: t.meta, color: '#0f380f' },
|
||||||
|
{ tag: t.attributeName, color: '#0f380f' },
|
||||||
|
{ tag: t.propertyName, color: '#0f380f' },
|
||||||
|
{ tag: t.className, color: '#0f380f' },
|
||||||
|
{ tag: t.invalid, color: '#0f380f' },
|
||||||
|
],
|
||||||
|
});
|
||||||
37
packages/react/src/themes/blackscreen.js
Normal file
37
packages/react/src/themes/blackscreen.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
|
export const settings = {
|
||||||
|
background: 'black',
|
||||||
|
foreground: 'white', // whats that?
|
||||||
|
caret: 'white',
|
||||||
|
selection: '#ffffff20',
|
||||||
|
selectionMatch: '#036dd626',
|
||||||
|
lineHighlight: '#ffffff10',
|
||||||
|
lineBackground: '#00000050',
|
||||||
|
gutterBackground: 'transparent',
|
||||||
|
gutterForeground: '#8a919966',
|
||||||
|
};
|
||||||
|
export default createTheme({
|
||||||
|
theme: 'dark',
|
||||||
|
settings,
|
||||||
|
styles: [
|
||||||
|
{ tag: t.keyword, color: 'white' },
|
||||||
|
{ tag: t.operator, color: 'white' },
|
||||||
|
{ tag: t.special(t.variableName), color: 'white' },
|
||||||
|
{ tag: t.typeName, color: 'white' },
|
||||||
|
{ tag: t.atom, color: 'white' },
|
||||||
|
{ tag: t.number, color: 'white' },
|
||||||
|
{ tag: t.definition(t.variableName), color: 'white' },
|
||||||
|
{ tag: t.string, color: 'white' },
|
||||||
|
{ tag: t.special(t.string), color: 'white' },
|
||||||
|
{ tag: t.comment, color: 'white' },
|
||||||
|
{ tag: t.variableName, color: 'white' },
|
||||||
|
{ tag: t.tagName, color: 'white' },
|
||||||
|
{ tag: t.bracket, color: 'white' },
|
||||||
|
{ tag: t.meta, color: 'white' },
|
||||||
|
{ tag: t.attributeName, color: 'white' },
|
||||||
|
{ tag: t.propertyName, color: 'white' },
|
||||||
|
{ tag: t.className, color: 'white' },
|
||||||
|
{ tag: t.invalid, color: 'white' },
|
||||||
|
],
|
||||||
|
});
|
||||||
40
packages/react/src/themes/bluescreen.js
Normal file
40
packages/react/src/themes/bluescreen.js
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
|
export const settings = {
|
||||||
|
background: '#051DB5',
|
||||||
|
lineBackground: '#051DB550',
|
||||||
|
foreground: 'white', // whats that?
|
||||||
|
caret: 'white',
|
||||||
|
selection: 'rgba(128, 203, 196, 0.5)',
|
||||||
|
selectionMatch: '#036dd626',
|
||||||
|
// lineHighlight: '#8a91991a', // original
|
||||||
|
lineHighlight: '#00000050',
|
||||||
|
gutterBackground: 'transparent',
|
||||||
|
// gutterForeground: '#8a919966',
|
||||||
|
gutterForeground: '#8a919966',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createTheme({
|
||||||
|
theme: 'dark',
|
||||||
|
settings,
|
||||||
|
styles: [
|
||||||
|
{ tag: t.keyword, color: 'white' },
|
||||||
|
{ tag: t.operator, color: 'white' },
|
||||||
|
{ tag: t.special(t.variableName), color: 'white' },
|
||||||
|
{ tag: t.typeName, color: 'white' },
|
||||||
|
{ tag: t.atom, color: 'white' },
|
||||||
|
{ tag: t.number, color: 'white' },
|
||||||
|
{ tag: t.definition(t.variableName), color: 'white' },
|
||||||
|
{ tag: t.string, color: 'white' },
|
||||||
|
{ tag: t.special(t.string), color: 'white' },
|
||||||
|
{ tag: t.comment, color: 'white' },
|
||||||
|
{ tag: t.variableName, color: 'white' },
|
||||||
|
{ tag: t.tagName, color: 'white' },
|
||||||
|
{ tag: t.bracket, color: 'white' },
|
||||||
|
{ tag: t.meta, color: 'white' },
|
||||||
|
{ tag: t.attributeName, color: 'white' },
|
||||||
|
{ tag: t.propertyName, color: 'white' },
|
||||||
|
{ tag: t.className, color: 'white' },
|
||||||
|
{ tag: t.invalid, color: 'white' },
|
||||||
|
],
|
||||||
|
});
|
||||||
36
packages/react/src/themes/terminal.js
Normal file
36
packages/react/src/themes/terminal.js
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
|
export const settings = {
|
||||||
|
background: 'black',
|
||||||
|
foreground: '#41FF00', // whats that?
|
||||||
|
caret: '#41FF00',
|
||||||
|
selection: '#ffffff20',
|
||||||
|
selectionMatch: '#036dd626',
|
||||||
|
lineHighlight: '#ffffff10',
|
||||||
|
gutterBackground: 'transparent',
|
||||||
|
gutterForeground: '#8a919966',
|
||||||
|
};
|
||||||
|
export default createTheme({
|
||||||
|
theme: 'dark',
|
||||||
|
settings,
|
||||||
|
styles: [
|
||||||
|
{ tag: t.keyword, color: '#41FF00' },
|
||||||
|
{ tag: t.operator, color: '#41FF00' },
|
||||||
|
{ tag: t.special(t.variableName), color: '#41FF00' },
|
||||||
|
{ tag: t.typeName, color: '#41FF00' },
|
||||||
|
{ tag: t.atom, color: '#41FF00' },
|
||||||
|
{ tag: t.number, color: '#41FF00' },
|
||||||
|
{ tag: t.definition(t.variableName), color: '#41FF00' },
|
||||||
|
{ tag: t.string, color: '#41FF00' },
|
||||||
|
{ tag: t.special(t.string), color: '#41FF00' },
|
||||||
|
{ tag: t.comment, color: '#41FF00' },
|
||||||
|
{ tag: t.variableName, color: '#41FF00' },
|
||||||
|
{ tag: t.tagName, color: '#41FF00' },
|
||||||
|
{ tag: t.bracket, color: '#41FF00' },
|
||||||
|
{ tag: t.meta, color: '#41FF00' },
|
||||||
|
{ tag: t.attributeName, color: '#41FF00' },
|
||||||
|
{ tag: t.propertyName, color: '#41FF00' },
|
||||||
|
{ tag: t.className, color: '#41FF00' },
|
||||||
|
{ tag: t.invalid, color: '#41FF00' },
|
||||||
|
],
|
||||||
|
});
|
||||||
38
packages/react/src/themes/whitescreen.js
Normal file
38
packages/react/src/themes/whitescreen.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
|
export const settings = {
|
||||||
|
background: 'white',
|
||||||
|
foreground: 'black', // whats that?
|
||||||
|
caret: 'black',
|
||||||
|
selection: 'rgba(128, 203, 196, 0.5)',
|
||||||
|
selectionMatch: '#ffffff26',
|
||||||
|
lineHighlight: '#cccccc50',
|
||||||
|
lineBackground: '#ffffff50',
|
||||||
|
gutterBackground: 'transparent',
|
||||||
|
gutterForeground: 'black',
|
||||||
|
light: true,
|
||||||
|
};
|
||||||
|
export default createTheme({
|
||||||
|
theme: 'light',
|
||||||
|
settings,
|
||||||
|
styles: [
|
||||||
|
{ tag: t.keyword, color: 'black' },
|
||||||
|
{ tag: t.operator, color: 'black' },
|
||||||
|
{ tag: t.special(t.variableName), color: 'black' },
|
||||||
|
{ tag: t.typeName, color: 'black' },
|
||||||
|
{ tag: t.atom, color: 'black' },
|
||||||
|
{ tag: t.number, color: 'black' },
|
||||||
|
{ tag: t.definition(t.variableName), color: 'black' },
|
||||||
|
{ tag: t.string, color: 'black' },
|
||||||
|
{ tag: t.special(t.string), color: 'black' },
|
||||||
|
{ tag: t.comment, color: 'black' },
|
||||||
|
{ tag: t.variableName, color: 'black' },
|
||||||
|
{ tag: t.tagName, color: 'black' },
|
||||||
|
{ tag: t.bracket, color: 'black' },
|
||||||
|
{ tag: t.meta, color: 'black' },
|
||||||
|
{ tag: t.attributeName, color: 'black' },
|
||||||
|
{ tag: t.propertyName, color: 'black' },
|
||||||
|
{ tag: t.className, color: 'black' },
|
||||||
|
{ tag: t.invalid, color: 'black' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
@ -193,21 +193,9 @@ function effectSend(input, effect, wet) {
|
||||||
// export const webaudioOutput = async (t, hap, ct, cps) => {
|
// export const webaudioOutput = async (t, hap, ct, cps) => {
|
||||||
export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
export const webaudioOutput = async (hap, deadline, hapDuration) => {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
/* if (isNote(hap.value)) {
|
hap.ensureObjectValue();
|
||||||
// supports primitive hap values that look like notes
|
|
||||||
hap.value = { note: hap.value };
|
// calculate absolute time
|
||||||
} */
|
|
||||||
if (typeof hap.value !== 'object') {
|
|
||||||
logger(
|
|
||||||
`hap.value "${hap.value}" is not supported by webaudio output. Hint: append .note() or .s() to the end`,
|
|
||||||
'error',
|
|
||||||
);
|
|
||||||
/* throw new Error(
|
|
||||||
`hap.value "${hap.value}"" is not supported by webaudio output. Hint: append .note() or .s() to the end`,
|
|
||||||
); */
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// calculate correct time (tone.js workaround)
|
|
||||||
let t = ac.currentTime + deadline;
|
let t = ac.currentTime + deadline;
|
||||||
// destructure value
|
// destructure value
|
||||||
let {
|
let {
|
||||||
|
|
|
||||||
145
pnpm-lock.yaml
generated
145
pnpm-lock.yaml
generated
|
|
@ -164,6 +164,8 @@ importers:
|
||||||
'@codemirror/state': ^6.2.0
|
'@codemirror/state': ^6.2.0
|
||||||
'@codemirror/view': ^6.7.3
|
'@codemirror/view': ^6.7.3
|
||||||
'@lezer/highlight': ^1.1.3
|
'@lezer/highlight': ^1.1.3
|
||||||
|
'@replit/codemirror-emacs': ^6.0.0
|
||||||
|
'@replit/codemirror-vim': ^6.0.6
|
||||||
'@strudel.cycles/core': workspace:*
|
'@strudel.cycles/core': workspace:*
|
||||||
'@strudel.cycles/transpiler': workspace:*
|
'@strudel.cycles/transpiler': workspace:*
|
||||||
'@strudel.cycles/webaudio': workspace:*
|
'@strudel.cycles/webaudio': workspace:*
|
||||||
|
|
@ -185,6 +187,8 @@ importers:
|
||||||
'@codemirror/state': 6.2.0
|
'@codemirror/state': 6.2.0
|
||||||
'@codemirror/view': 6.7.3
|
'@codemirror/view': 6.7.3
|
||||||
'@lezer/highlight': 1.1.3
|
'@lezer/highlight': 1.1.3
|
||||||
|
'@replit/codemirror-emacs': 6.0.0_cgfc5aojxuwjajwhkrgidrzxoa
|
||||||
|
'@replit/codemirror-vim': 6.0.6_a4vbhepr4qhxm5cldqd4jpyase
|
||||||
'@strudel.cycles/core': link:../core
|
'@strudel.cycles/core': link:../core
|
||||||
'@strudel.cycles/transpiler': link:../transpiler
|
'@strudel.cycles/transpiler': link:../transpiler
|
||||||
'@strudel.cycles/webaudio': link:../webaudio
|
'@strudel.cycles/webaudio': link:../webaudio
|
||||||
|
|
@ -358,6 +362,8 @@ importers:
|
||||||
'@docsearch/react': ^3.1.0
|
'@docsearch/react': ^3.1.0
|
||||||
'@headlessui/react': ^1.7.7
|
'@headlessui/react': ^1.7.7
|
||||||
'@heroicons/react': ^2.0.13
|
'@heroicons/react': ^2.0.13
|
||||||
|
'@nanostores/persistent': ^0.7.0
|
||||||
|
'@nanostores/react': ^0.4.1
|
||||||
'@strudel.cycles/core': workspace:*
|
'@strudel.cycles/core': workspace:*
|
||||||
'@strudel.cycles/csound': workspace:*
|
'@strudel.cycles/csound': workspace:*
|
||||||
'@strudel.cycles/midi': workspace:*
|
'@strudel.cycles/midi': workspace:*
|
||||||
|
|
@ -371,6 +377,7 @@ importers:
|
||||||
'@strudel.cycles/webaudio': workspace:*
|
'@strudel.cycles/webaudio': workspace:*
|
||||||
'@strudel.cycles/xen': workspace:*
|
'@strudel.cycles/xen': workspace:*
|
||||||
'@supabase/supabase-js': ^1.35.3
|
'@supabase/supabase-js': ^1.35.3
|
||||||
|
'@tailwindcss/forms': ^0.5.3
|
||||||
'@tailwindcss/typography': ^0.5.8
|
'@tailwindcss/typography': ^0.5.8
|
||||||
'@types/node': ^18.0.0
|
'@types/node': ^18.0.0
|
||||||
'@types/react': ^18.0.26
|
'@types/react': ^18.0.26
|
||||||
|
|
@ -382,6 +389,7 @@ importers:
|
||||||
fraction.js: ^4.2.0
|
fraction.js: ^4.2.0
|
||||||
html-escaper: ^3.0.3
|
html-escaper: ^3.0.3
|
||||||
nanoid: ^4.0.0
|
nanoid: ^4.0.0
|
||||||
|
nanostores: ^0.7.4
|
||||||
preact: ^10.7.3
|
preact: ^10.7.3
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
react-dom: ^18.2.0
|
react-dom: ^18.2.0
|
||||||
|
|
@ -402,6 +410,8 @@ importers:
|
||||||
'@docsearch/react': 3.3.2_y6lbs4o5th67cuzjdmtw5eqh7a
|
'@docsearch/react': 3.3.2_y6lbs4o5th67cuzjdmtw5eqh7a
|
||||||
'@headlessui/react': 1.7.8_biqbaboplfbrettd7655fr4n2y
|
'@headlessui/react': 1.7.8_biqbaboplfbrettd7655fr4n2y
|
||||||
'@heroicons/react': 2.0.14_react@18.2.0
|
'@heroicons/react': 2.0.14_react@18.2.0
|
||||||
|
'@nanostores/persistent': 0.7.0_nanostores@0.7.4
|
||||||
|
'@nanostores/react': 0.4.1_nkfnbc2tpc77iht7asm3uqwau4
|
||||||
'@strudel.cycles/core': link:../packages/core
|
'@strudel.cycles/core': link:../packages/core
|
||||||
'@strudel.cycles/csound': link:../packages/csound
|
'@strudel.cycles/csound': link:../packages/csound
|
||||||
'@strudel.cycles/midi': link:../packages/midi
|
'@strudel.cycles/midi': link:../packages/midi
|
||||||
|
|
@ -415,6 +425,7 @@ importers:
|
||||||
'@strudel.cycles/webaudio': link:../packages/webaudio
|
'@strudel.cycles/webaudio': link:../packages/webaudio
|
||||||
'@strudel.cycles/xen': link:../packages/xen
|
'@strudel.cycles/xen': link:../packages/xen
|
||||||
'@supabase/supabase-js': 1.35.7
|
'@supabase/supabase-js': 1.35.7
|
||||||
|
'@tailwindcss/forms': 0.5.3_tailwindcss@3.2.4
|
||||||
'@tailwindcss/typography': 0.5.9_tailwindcss@3.2.4
|
'@tailwindcss/typography': 0.5.9_tailwindcss@3.2.4
|
||||||
'@types/node': 18.11.18
|
'@types/node': 18.11.18
|
||||||
'@types/react': 18.0.27
|
'@types/react': 18.0.27
|
||||||
|
|
@ -424,6 +435,7 @@ importers:
|
||||||
canvas: 2.11.0
|
canvas: 2.11.0
|
||||||
fraction.js: 4.2.0
|
fraction.js: 4.2.0
|
||||||
nanoid: 4.0.0
|
nanoid: 4.0.0
|
||||||
|
nanostores: 0.7.4
|
||||||
preact: 10.11.3
|
preact: 10.11.3
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
|
@ -3164,6 +3176,27 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@nanostores/persistent/0.7.0_nanostores@0.7.4:
|
||||||
|
resolution: {integrity: sha512-4PAInL/T1hbftZUJ0cmgdFHBMalUoq7BUXFBy7QfyMv/8X3LPTYNh/yxspL7+J+XM3UNvVI7IFRMMs6FBasjhQ==}
|
||||||
|
engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
nanostores: ^0.7.0
|
||||||
|
dependencies:
|
||||||
|
nanostores: 0.7.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@nanostores/react/0.4.1_nkfnbc2tpc77iht7asm3uqwau4:
|
||||||
|
resolution: {integrity: sha512-lsv0CYrMxczbXtoV/mxFVEoL/uVjEjseoP89srO/5yNAOkJka+dSFS7LYyWEbuvCPO7EgbtkvRpO5V+OztKQOw==}
|
||||||
|
engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
nanostores: ^0.7.0
|
||||||
|
react: '>=18.0.0'
|
||||||
|
dependencies:
|
||||||
|
nanostores: 0.7.4
|
||||||
|
react: 18.2.0
|
||||||
|
use-sync-external-store: 1.2.0_react@18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@nodelib/fs.scandir/2.1.5:
|
/@nodelib/fs.scandir/2.1.5:
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
@ -3400,6 +3433,33 @@ packages:
|
||||||
tsm: 2.3.0
|
tsm: 2.3.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@replit/codemirror-emacs/6.0.0_cgfc5aojxuwjajwhkrgidrzxoa:
|
||||||
|
resolution: {integrity: sha512-zxSDg3UKm7811hjqNtgvK9G0IBtCWf82Idb9nZQo0ldmGl4d9SV7oCSuXQ58NmOG4AV7coD7kgFSZhEqHhyQhA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@codemirror/autocomplete': ^6.0.2
|
||||||
|
'@codemirror/commands': ^6.0.0
|
||||||
|
'@codemirror/search': ^6.0.0
|
||||||
|
'@codemirror/state': ^6.0.1
|
||||||
|
'@codemirror/view': ^6.0.2
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/autocomplete': 6.4.0_a4vbhepr4qhxm5cldqd4jpyase
|
||||||
|
'@codemirror/state': 6.2.0
|
||||||
|
'@codemirror/view': 6.7.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@replit/codemirror-vim/6.0.6_a4vbhepr4qhxm5cldqd4jpyase:
|
||||||
|
resolution: {integrity: sha512-/Lc+5AmV+T5pTm5P+rWpL+gseNHNye7xaUWpULczHai5ZLVg/ZE3+MBwK3Ai+/SmZKR/mK2YuXgNKnTGToEGYg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@codemirror/commands': ^6.0.0
|
||||||
|
'@codemirror/language': ^6.1.0
|
||||||
|
'@codemirror/search': ^6.2.0
|
||||||
|
'@codemirror/state': ^6.0.1
|
||||||
|
'@codemirror/view': ^6.0.3
|
||||||
|
dependencies:
|
||||||
|
'@codemirror/state': 6.2.0
|
||||||
|
'@codemirror/view': 6.7.3
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@rollup/plugin-babel/5.3.1_3dsfpkpoyvuuxyfgdbpn4j4uzm:
|
/@rollup/plugin-babel/5.3.1_3dsfpkpoyvuuxyfgdbpn4j4uzm:
|
||||||
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
|
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
|
|
@ -3560,6 +3620,15 @@ packages:
|
||||||
string.prototype.matchall: 4.0.8
|
string.prototype.matchall: 4.0.8
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@tailwindcss/forms/0.5.3_tailwindcss@3.2.4:
|
||||||
|
resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==}
|
||||||
|
peerDependencies:
|
||||||
|
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
|
||||||
|
dependencies:
|
||||||
|
mini-svg-data-uri: 1.4.4
|
||||||
|
tailwindcss: 3.2.4
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@tailwindcss/typography/0.5.9_tailwindcss@3.2.4:
|
/@tailwindcss/typography/0.5.9_tailwindcss@3.2.4:
|
||||||
resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==}
|
resolution: {integrity: sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -9371,6 +9440,11 @@ packages:
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/mini-svg-data-uri/1.4.4:
|
||||||
|
resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/minimatch/3.1.2:
|
/minimatch/3.1.2:
|
||||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -9599,6 +9673,11 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/nanostores/0.7.4:
|
||||||
|
resolution: {integrity: sha512-MBeUVt7NBcXqh7AGT+KSr3O0X/995CZsvcP2QEMP+PXFwb07qv3Vjyq+EX0yS8f12Vv3Tn2g/BvK/OZoMhJlOQ==}
|
||||||
|
engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/napi-build-utils/1.0.2:
|
/napi-build-utils/1.0.2:
|
||||||
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
|
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
@ -10413,17 +10492,6 @@ packages:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/postcss-import/14.1.0:
|
|
||||||
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
|
|
||||||
engines: {node: '>=10.0.0'}
|
|
||||||
peerDependencies:
|
|
||||||
postcss: ^8.0.0
|
|
||||||
dependencies:
|
|
||||||
postcss-value-parser: 4.2.0
|
|
||||||
read-cache: 1.0.0
|
|
||||||
resolve: 1.22.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/postcss-import/14.1.0_postcss@8.4.21:
|
/postcss-import/14.1.0_postcss@8.4.21:
|
||||||
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
|
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
@ -10434,16 +10502,6 @@ packages:
|
||||||
postcss-value-parser: 4.2.0
|
postcss-value-parser: 4.2.0
|
||||||
read-cache: 1.0.0
|
read-cache: 1.0.0
|
||||||
resolve: 1.22.1
|
resolve: 1.22.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/postcss-js/4.0.0:
|
|
||||||
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
|
|
||||||
engines: {node: ^12 || ^14 || >= 16}
|
|
||||||
peerDependencies:
|
|
||||||
postcss: ^8.3.3
|
|
||||||
dependencies:
|
|
||||||
camelcase-css: 2.0.1
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/postcss-js/4.0.0_postcss@8.4.21:
|
/postcss-js/4.0.0_postcss@8.4.21:
|
||||||
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
|
resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==}
|
||||||
|
|
@ -10453,23 +10511,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
camelcase-css: 2.0.1
|
camelcase-css: 2.0.1
|
||||||
postcss: 8.4.21
|
postcss: 8.4.21
|
||||||
dev: true
|
|
||||||
|
|
||||||
/postcss-load-config/3.1.4:
|
|
||||||
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
|
|
||||||
engines: {node: '>= 10'}
|
|
||||||
peerDependencies:
|
|
||||||
postcss: '>=8.0.9'
|
|
||||||
ts-node: '>=9.0.0'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
postcss:
|
|
||||||
optional: true
|
|
||||||
ts-node:
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
lilconfig: 2.0.6
|
|
||||||
yaml: 1.10.2
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/postcss-load-config/3.1.4_postcss@8.4.21:
|
/postcss-load-config/3.1.4_postcss@8.4.21:
|
||||||
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
|
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
|
||||||
|
|
@ -10487,15 +10528,6 @@ packages:
|
||||||
postcss: 8.4.21
|
postcss: 8.4.21
|
||||||
yaml: 1.10.2
|
yaml: 1.10.2
|
||||||
|
|
||||||
/postcss-nested/6.0.0:
|
|
||||||
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
|
|
||||||
engines: {node: '>=12.0'}
|
|
||||||
peerDependencies:
|
|
||||||
postcss: ^8.2.14
|
|
||||||
dependencies:
|
|
||||||
postcss-selector-parser: 6.0.11
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/postcss-nested/6.0.0_postcss@8.4.21:
|
/postcss-nested/6.0.0_postcss@8.4.21:
|
||||||
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
|
resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
|
||||||
engines: {node: '>=12.0'}
|
engines: {node: '>=12.0'}
|
||||||
|
|
@ -10504,7 +10536,6 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss: 8.4.21
|
postcss: 8.4.21
|
||||||
postcss-selector-parser: 6.0.11
|
postcss-selector-parser: 6.0.11
|
||||||
dev: true
|
|
||||||
|
|
||||||
/postcss-selector-parser/6.0.10:
|
/postcss-selector-parser/6.0.10:
|
||||||
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
|
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
|
||||||
|
|
@ -12127,8 +12158,6 @@ packages:
|
||||||
resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==}
|
resolution: {integrity: sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==}
|
||||||
engines: {node: '>=12.13.0'}
|
engines: {node: '>=12.13.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
|
||||||
postcss: ^8.0.9
|
|
||||||
dependencies:
|
dependencies:
|
||||||
arg: 5.0.2
|
arg: 5.0.2
|
||||||
chokidar: 3.5.3
|
chokidar: 3.5.3
|
||||||
|
|
@ -12145,10 +12174,10 @@ packages:
|
||||||
object-hash: 3.0.0
|
object-hash: 3.0.0
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
postcss: 8.4.21
|
postcss: 8.4.21
|
||||||
postcss-import: 14.1.0
|
postcss-import: 14.1.0_postcss@8.4.21
|
||||||
postcss-js: 4.0.0
|
postcss-js: 4.0.0_postcss@8.4.21
|
||||||
postcss-load-config: 3.1.4
|
postcss-load-config: 3.1.4_postcss@8.4.21
|
||||||
postcss-nested: 6.0.0
|
postcss-nested: 6.0.0_postcss@8.4.21
|
||||||
postcss-selector-parser: 6.0.11
|
postcss-selector-parser: 6.0.11
|
||||||
postcss-value-parser: 4.2.0
|
postcss-value-parser: 4.2.0
|
||||||
quick-lru: 5.1.1
|
quick-lru: 5.1.1
|
||||||
|
|
@ -12854,6 +12883,14 @@ packages:
|
||||||
punycode: 2.3.0
|
punycode: 2.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/use-sync-external-store/1.2.0_react@18.2.0:
|
||||||
|
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/utf-8-validate/5.0.10:
|
/utf-8-validate/5.0.10:
|
||||||
resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==}
|
resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==}
|
||||||
engines: {node: '>=6.14.2'}
|
engines: {node: '>=6.14.2'}
|
||||||
|
|
|
||||||
|
|
@ -3690,6 +3690,39 @@ exports[`runs examples > example "sine" example index 0 1`] = `
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "slice" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ (15/16 → 1/1) ⇝ 9/8 | begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 3/4 → 15/16 | begin:0.375 end:0.5 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 21/32 → 3/4 | begin:0.5 end:0.625 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 9/16 → 21/32 | begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 3/8 → 9/16 | begin:0.625 end:0.75 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 3/16 → 3/8 | begin:0.75 end:0.875 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 0/1 → 3/16 | begin:0.875 end:1 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 21/16 → 3/2 | begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 9/8 → 21/16 | begin:0.125 end:0.25 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 15/16 ⇜ (1/1 → 9/8) | begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 3/2 → 27/16 | begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 27/16 → 15/8 | begin:0.125 end:0.25 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 15/8 → 63/32 | begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
|
||||||
|
"[ (63/32 → 2/1) ⇝ 33/16 | begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 63/32 ⇜ (2/1 → 33/16) | begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 33/16 → 9/4 | begin:0.375 end:0.5 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 9/4 → 75/32 | begin:0.5 end:0.625 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 75/32 → 39/16 | begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 39/16 → 21/8 | begin:0.625 end:0.75 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 21/8 → 45/16 | begin:0.75 end:0.875 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 45/16 → 3/1 | begin:0.875 end:1 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 3/1 → 51/16 | begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 51/16 → 27/8 | begin:0.125 end:0.25 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 27/8 → 57/16 | begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 57/16 → 15/4 | begin:0.375 end:0.5 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 15/4 → 123/32 | begin:0.5 end:0.625 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 123/32 → 63/16 | begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ (63/16 → 4/1) ⇝ 33/8 | begin:0.625 end:0.75 _slices:8 s:breaks165 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "slow" example index 0 1`] = `
|
exports[`runs examples > example "slow" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/1 | s:bd ]",
|
"[ 0/1 → 1/1 | s:bd ]",
|
||||||
|
|
@ -3815,6 +3848,36 @@ exports[`runs examples > example "speed" example index 1 1`] = `
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "splice" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 5/26 | speed:0.65 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 5/26 → 5/13 | speed:0.65 unit:c begin:0.125 end:0.25 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 5/13 → 20/39 | speed:0.9750000000000001 unit:c begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 20/39 → 25/39 | speed:0.9750000000000001 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 25/39 → 10/13 | speed:0.9750000000000001 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 10/13 → 25/26 | speed:0.65 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]",
|
||||||
|
"[ (25/26 → 1/1) ⇝ 35/26 | speed:0.325 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 25/26 ⇜ (1/1 → 35/26) | speed:0.325 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 35/26 → 20/13 | speed:0.65 unit:c begin:0.875 end:1 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 20/13 → 45/26 | speed:0.65 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 45/26 → 25/13 | speed:0.65 unit:c begin:0.125 end:0.25 _slices:8 s:breaks165 ]",
|
||||||
|
"[ (25/13 → 2/1) ⇝ 80/39 | speed:0.9750000000000001 unit:c begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 25/13 ⇜ (2/1 → 80/39) | speed:0.9750000000000001 unit:c begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 80/39 → 85/39 | speed:0.9750000000000001 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 85/39 → 30/13 | speed:0.9750000000000001 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 30/13 → 5/2 | speed:0.65 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 5/2 → 75/26 | speed:0.325 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ (75/26 → 3/1) ⇝ 40/13 | speed:0.65 unit:c begin:0.875 end:1 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 75/26 ⇜ (3/1 → 40/13) | speed:0.65 unit:c begin:0.875 end:1 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 40/13 → 85/26 | speed:0.65 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 85/26 → 45/13 | speed:0.65 unit:c begin:0.125 end:0.25 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 45/13 → 140/39 | speed:0.9750000000000001 unit:c begin:0.25 end:0.375 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 140/39 → 145/39 | speed:0.9750000000000001 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]",
|
||||||
|
"[ 145/39 → 50/13 | speed:0.9750000000000001 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]",
|
||||||
|
"[ (50/13 → 4/1) ⇝ 105/26 | speed:0.65 unit:c begin:0.375 end:0.5 _slices:8 s:breaks165 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "square" example index 0 1`] = `
|
exports[`runs examples > example "square" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/2 | note:C3 ]",
|
"[ 0/1 → 1/2 | note:C3 ]",
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@
|
||||||
"@docsearch/react": "^3.1.0",
|
"@docsearch/react": "^3.1.0",
|
||||||
"@headlessui/react": "^1.7.7",
|
"@headlessui/react": "^1.7.7",
|
||||||
"@heroicons/react": "^2.0.13",
|
"@heroicons/react": "^2.0.13",
|
||||||
|
"@nanostores/persistent": "^0.7.0",
|
||||||
|
"@nanostores/react": "^0.4.1",
|
||||||
"@strudel.cycles/core": "workspace:*",
|
"@strudel.cycles/core": "workspace:*",
|
||||||
"@strudel.cycles/csound": "workspace:*",
|
"@strudel.cycles/csound": "workspace:*",
|
||||||
"@strudel.cycles/midi": "workspace:*",
|
"@strudel.cycles/midi": "workspace:*",
|
||||||
|
|
@ -34,6 +36,7 @@
|
||||||
"@strudel.cycles/webaudio": "workspace:*",
|
"@strudel.cycles/webaudio": "workspace:*",
|
||||||
"@strudel.cycles/xen": "workspace:*",
|
"@strudel.cycles/xen": "workspace:*",
|
||||||
"@supabase/supabase-js": "^1.35.3",
|
"@supabase/supabase-js": "^1.35.3",
|
||||||
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
"@tailwindcss/typography": "^0.5.8",
|
"@tailwindcss/typography": "^0.5.8",
|
||||||
"@types/node": "^18.0.0",
|
"@types/node": "^18.0.0",
|
||||||
"@types/react": "^18.0.26",
|
"@types/react": "^18.0.26",
|
||||||
|
|
@ -43,6 +46,7 @@
|
||||||
"canvas": "^2.11.0",
|
"canvas": "^2.11.0",
|
||||||
"fraction.js": "^4.2.0",
|
"fraction.js": "^4.2.0",
|
||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
|
"nanostores": "^0.7.4",
|
||||||
"preact": "^10.7.3",
|
"preact": "^10.7.3",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|
|
||||||
BIN
website/public/fonts/3270/3270-Regular.ttf
Normal file
BIN
website/public/fonts/3270/3270-Regular.ttf
Normal file
Binary file not shown.
51
website/public/fonts/3270/LICENSE.txt
Normal file
51
website/public/fonts/3270/LICENSE.txt
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
Copyright 2022 The 3270font Authors (https://github.com/rbanffy/3270font)
|
||||||
|
|
||||||
|
Copyright (c) 2011-2022, Ricardo Banffy.
|
||||||
|
Copyright (c) 1993-2011, Paul Mattes.
|
||||||
|
Copyright (c) 2004-2005, Don Russell.
|
||||||
|
Copyright (c) 2004, Dick Altenbern.
|
||||||
|
Copyright (c) 1990, Jeff Sparkes.
|
||||||
|
Copyright (c) 1989, Georgia Tech Research Corporation (GTRC), Atlanta, GA 30332.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of Ricardo Banffy, Paul Mattes, Don Russell,
|
||||||
|
Dick Altenbern, Jeff Sparkes, GTRC nor the names of their contributors
|
||||||
|
may be used to endorse or promote products derived from this software
|
||||||
|
without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
IN NO EVENT SHALL RICARDO BANFFY, PAUL MATTES, DON RUSSELL, DICK ALTENBERN, JEFF
|
||||||
|
SPARKES OR GTRC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||||||
|
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
The Debian Logo glyph is based on the Debian Open Use Logo and is
|
||||||
|
Copyright (c) 1999 Software in the Public Interest, Inc., and it is
|
||||||
|
incorporated here under the terms of the Creative Commons
|
||||||
|
Attribution-ShareAlike 3.0 Unported License. The logo is released
|
||||||
|
under the terms of the GNU Lesser General Public License, version 3 or
|
||||||
|
any later version, or, at your option, of the Creative Commons
|
||||||
|
Attribution-ShareAlike 3.0 Unported License.
|
||||||
|
|
||||||
|
Ubuntu, the Ubuntu logo and the Circle of Friends symbol are
|
||||||
|
registered trademarks of Canonical Ltd.
|
||||||
|
|
||||||
|
The Fontforge SFD font description file is optionally licensed under
|
||||||
|
the SIL Open Font License v1.1 with no Reserved Font Name. This
|
||||||
|
license is available with a FAQ at http://scripts.sil.org/OFL.
|
||||||
BIN
website/public/fonts/BigBlueTerminal/BigBlue_TerminalPlus.TTF
Normal file
BIN
website/public/fonts/BigBlueTerminal/BigBlue_TerminalPlus.TTF
Normal file
Binary file not shown.
428
website/public/fonts/BigBlueTerminal/LICENSE.txt
Normal file
428
website/public/fonts/BigBlueTerminal/LICENSE.txt
Normal file
|
|
@ -0,0 +1,428 @@
|
||||||
|
Attribution-ShareAlike 4.0 International
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||||
|
does not provide legal services or legal advice. Distribution of
|
||||||
|
Creative Commons public licenses does not create a lawyer-client or
|
||||||
|
other relationship. Creative Commons makes its licenses and related
|
||||||
|
information available on an "as-is" basis. Creative Commons gives no
|
||||||
|
warranties regarding its licenses, any material licensed under their
|
||||||
|
terms and conditions, or any related information. Creative Commons
|
||||||
|
disclaims all liability for damages resulting from their use to the
|
||||||
|
fullest extent possible.
|
||||||
|
|
||||||
|
Using Creative Commons Public Licenses
|
||||||
|
|
||||||
|
Creative Commons public licenses provide a standard set of terms and
|
||||||
|
conditions that creators and other rights holders may use to share
|
||||||
|
original works of authorship and other material subject to copyright
|
||||||
|
and certain other rights specified in the public license below. The
|
||||||
|
following considerations are for informational purposes only, are not
|
||||||
|
exhaustive, and do not form part of our licenses.
|
||||||
|
|
||||||
|
Considerations for licensors: Our public licenses are
|
||||||
|
intended for use by those authorized to give the public
|
||||||
|
permission to use material in ways otherwise restricted by
|
||||||
|
copyright and certain other rights. Our licenses are
|
||||||
|
irrevocable. Licensors should read and understand the terms
|
||||||
|
and conditions of the license they choose before applying it.
|
||||||
|
Licensors should also secure all rights necessary before
|
||||||
|
applying our licenses so that the public can reuse the
|
||||||
|
material as expected. Licensors should clearly mark any
|
||||||
|
material not subject to the license. This includes other CC-
|
||||||
|
licensed material, or material used under an exception or
|
||||||
|
limitation to copyright. More considerations for licensors:
|
||||||
|
wiki.creativecommons.org/Considerations_for_licensors
|
||||||
|
|
||||||
|
Considerations for the public: By using one of our public
|
||||||
|
licenses, a licensor grants the public permission to use the
|
||||||
|
licensed material under specified terms and conditions. If
|
||||||
|
the licensor's permission is not necessary for any reason--for
|
||||||
|
example, because of any applicable exception or limitation to
|
||||||
|
copyright--then that use is not regulated by the license. Our
|
||||||
|
licenses grant only permissions under copyright and certain
|
||||||
|
other rights that a licensor has authority to grant. Use of
|
||||||
|
the licensed material may still be restricted for other
|
||||||
|
reasons, including because others have copyright or other
|
||||||
|
rights in the material. A licensor may make special requests,
|
||||||
|
such as asking that all changes be marked or described.
|
||||||
|
Although not required by our licenses, you are encouraged to
|
||||||
|
respect those requests where reasonable. More_considerations
|
||||||
|
for the public:
|
||||||
|
wiki.creativecommons.org/Considerations_for_licensees
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons Attribution-ShareAlike 4.0 International Public
|
||||||
|
License
|
||||||
|
|
||||||
|
By exercising the Licensed Rights (defined below), You accept and agree
|
||||||
|
to be bound by the terms and conditions of this Creative Commons
|
||||||
|
Attribution-ShareAlike 4.0 International Public License ("Public
|
||||||
|
License"). To the extent this Public License may be interpreted as a
|
||||||
|
contract, You are granted the Licensed Rights in consideration of Your
|
||||||
|
acceptance of these terms and conditions, and the Licensor grants You
|
||||||
|
such rights in consideration of benefits the Licensor receives from
|
||||||
|
making the Licensed Material available under these terms and
|
||||||
|
conditions.
|
||||||
|
|
||||||
|
|
||||||
|
Section 1 -- Definitions.
|
||||||
|
|
||||||
|
a. Adapted Material means material subject to Copyright and Similar
|
||||||
|
Rights that is derived from or based upon the Licensed Material
|
||||||
|
and in which the Licensed Material is translated, altered,
|
||||||
|
arranged, transformed, or otherwise modified in a manner requiring
|
||||||
|
permission under the Copyright and Similar Rights held by the
|
||||||
|
Licensor. For purposes of this Public License, where the Licensed
|
||||||
|
Material is a musical work, performance, or sound recording,
|
||||||
|
Adapted Material is always produced where the Licensed Material is
|
||||||
|
synched in timed relation with a moving image.
|
||||||
|
|
||||||
|
b. Adapter's License means the license You apply to Your Copyright
|
||||||
|
and Similar Rights in Your contributions to Adapted Material in
|
||||||
|
accordance with the terms and conditions of this Public License.
|
||||||
|
|
||||||
|
c. BY-SA Compatible License means a license listed at
|
||||||
|
creativecommons.org/compatiblelicenses, approved by Creative
|
||||||
|
Commons as essentially the equivalent of this Public License.
|
||||||
|
|
||||||
|
d. Copyright and Similar Rights means copyright and/or similar rights
|
||||||
|
closely related to copyright including, without limitation,
|
||||||
|
performance, broadcast, sound recording, and Sui Generis Database
|
||||||
|
Rights, without regard to how the rights are labeled or
|
||||||
|
categorized. For purposes of this Public License, the rights
|
||||||
|
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||||
|
Rights.
|
||||||
|
|
||||||
|
e. Effective Technological Measures means those measures that, in the
|
||||||
|
absence of proper authority, may not be circumvented under laws
|
||||||
|
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||||
|
Treaty adopted on December 20, 1996, and/or similar international
|
||||||
|
agreements.
|
||||||
|
|
||||||
|
f. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||||
|
any other exception or limitation to Copyright and Similar Rights
|
||||||
|
that applies to Your use of the Licensed Material.
|
||||||
|
|
||||||
|
g. License Elements means the license attributes listed in the name
|
||||||
|
of a Creative Commons Public License. The License Elements of this
|
||||||
|
Public License are Attribution and ShareAlike.
|
||||||
|
|
||||||
|
h. Licensed Material means the artistic or literary work, database,
|
||||||
|
or other material to which the Licensor applied this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
i. Licensed Rights means the rights granted to You subject to the
|
||||||
|
terms and conditions of this Public License, which are limited to
|
||||||
|
all Copyright and Similar Rights that apply to Your use of the
|
||||||
|
Licensed Material and that the Licensor has authority to license.
|
||||||
|
|
||||||
|
j. Licensor means the individual(s) or entity(ies) granting rights
|
||||||
|
under this Public License.
|
||||||
|
|
||||||
|
k. Share means to provide material to the public by any means or
|
||||||
|
process that requires permission under the Licensed Rights, such
|
||||||
|
as reproduction, public display, public performance, distribution,
|
||||||
|
dissemination, communication, or importation, and to make material
|
||||||
|
available to the public including in ways that members of the
|
||||||
|
public may access the material from a place and at a time
|
||||||
|
individually chosen by them.
|
||||||
|
|
||||||
|
l. Sui Generis Database Rights means rights other than copyright
|
||||||
|
resulting from Directive 96/9/EC of the European Parliament and of
|
||||||
|
the Council of 11 March 1996 on the legal protection of databases,
|
||||||
|
as amended and/or succeeded, as well as other essentially
|
||||||
|
equivalent rights anywhere in the world.
|
||||||
|
|
||||||
|
m. You means the individual or entity exercising the Licensed Rights
|
||||||
|
under this Public License. Your has a corresponding meaning.
|
||||||
|
|
||||||
|
|
||||||
|
Section 2 -- Scope.
|
||||||
|
|
||||||
|
a. License grant.
|
||||||
|
|
||||||
|
1. Subject to the terms and conditions of this Public License,
|
||||||
|
the Licensor hereby grants You a worldwide, royalty-free,
|
||||||
|
non-sublicensable, non-exclusive, irrevocable license to
|
||||||
|
exercise the Licensed Rights in the Licensed Material to:
|
||||||
|
|
||||||
|
a. reproduce and Share the Licensed Material, in whole or
|
||||||
|
in part; and
|
||||||
|
|
||||||
|
b. produce, reproduce, and Share Adapted Material.
|
||||||
|
|
||||||
|
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||||
|
Exceptions and Limitations apply to Your use, this Public
|
||||||
|
License does not apply, and You do not need to comply with
|
||||||
|
its terms and conditions.
|
||||||
|
|
||||||
|
3. Term. The term of this Public License is specified in Section
|
||||||
|
6(a).
|
||||||
|
|
||||||
|
4. Media and formats; technical modifications allowed. The
|
||||||
|
Licensor authorizes You to exercise the Licensed Rights in
|
||||||
|
all media and formats whether now known or hereafter created,
|
||||||
|
and to make technical modifications necessary to do so. The
|
||||||
|
Licensor waives and/or agrees not to assert any right or
|
||||||
|
authority to forbid You from making technical modifications
|
||||||
|
necessary to exercise the Licensed Rights, including
|
||||||
|
technical modifications necessary to circumvent Effective
|
||||||
|
Technological Measures. For purposes of this Public License,
|
||||||
|
simply making modifications authorized by this Section 2(a)
|
||||||
|
(4) never produces Adapted Material.
|
||||||
|
|
||||||
|
5. Downstream recipients.
|
||||||
|
|
||||||
|
a. Offer from the Licensor -- Licensed Material. Every
|
||||||
|
recipient of the Licensed Material automatically
|
||||||
|
receives an offer from the Licensor to exercise the
|
||||||
|
Licensed Rights under the terms and conditions of this
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
b. Additional offer from the Licensor -- Adapted Material.
|
||||||
|
Every recipient of Adapted Material from You
|
||||||
|
automatically receives an offer from the Licensor to
|
||||||
|
exercise the Licensed Rights in the Adapted Material
|
||||||
|
under the conditions of the Adapter's License You apply.
|
||||||
|
|
||||||
|
c. No downstream restrictions. You may not offer or impose
|
||||||
|
any additional or different terms or conditions on, or
|
||||||
|
apply any Effective Technological Measures to, the
|
||||||
|
Licensed Material if doing so restricts exercise of the
|
||||||
|
Licensed Rights by any recipient of the Licensed
|
||||||
|
Material.
|
||||||
|
|
||||||
|
6. No endorsement. Nothing in this Public License constitutes or
|
||||||
|
may be construed as permission to assert or imply that You
|
||||||
|
are, or that Your use of the Licensed Material is, connected
|
||||||
|
with, or sponsored, endorsed, or granted official status by,
|
||||||
|
the Licensor or others designated to receive attribution as
|
||||||
|
provided in Section 3(a)(1)(A)(i).
|
||||||
|
|
||||||
|
b. Other rights.
|
||||||
|
|
||||||
|
1. Moral rights, such as the right of integrity, are not
|
||||||
|
licensed under this Public License, nor are publicity,
|
||||||
|
privacy, and/or other similar personality rights; however, to
|
||||||
|
the extent possible, the Licensor waives and/or agrees not to
|
||||||
|
assert any such rights held by the Licensor to the limited
|
||||||
|
extent necessary to allow You to exercise the Licensed
|
||||||
|
Rights, but not otherwise.
|
||||||
|
|
||||||
|
2. Patent and trademark rights are not licensed under this
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
3. To the extent possible, the Licensor waives any right to
|
||||||
|
collect royalties from You for the exercise of the Licensed
|
||||||
|
Rights, whether directly or through a collecting society
|
||||||
|
under any voluntary or waivable statutory or compulsory
|
||||||
|
licensing scheme. In all other cases the Licensor expressly
|
||||||
|
reserves any right to collect such royalties.
|
||||||
|
|
||||||
|
|
||||||
|
Section 3 -- License Conditions.
|
||||||
|
|
||||||
|
Your exercise of the Licensed Rights is expressly made subject to the
|
||||||
|
following conditions.
|
||||||
|
|
||||||
|
a. Attribution.
|
||||||
|
|
||||||
|
1. If You Share the Licensed Material (including in modified
|
||||||
|
form), You must:
|
||||||
|
|
||||||
|
a. retain the following if it is supplied by the Licensor
|
||||||
|
with the Licensed Material:
|
||||||
|
|
||||||
|
i. identification of the creator(s) of the Licensed
|
||||||
|
Material and any others designated to receive
|
||||||
|
attribution, in any reasonable manner requested by
|
||||||
|
the Licensor (including by pseudonym if
|
||||||
|
designated);
|
||||||
|
|
||||||
|
ii. a copyright notice;
|
||||||
|
|
||||||
|
iii. a notice that refers to this Public License;
|
||||||
|
|
||||||
|
iv. a notice that refers to the disclaimer of
|
||||||
|
warranties;
|
||||||
|
|
||||||
|
v. a URI or hyperlink to the Licensed Material to the
|
||||||
|
extent reasonably practicable;
|
||||||
|
|
||||||
|
b. indicate if You modified the Licensed Material and
|
||||||
|
retain an indication of any previous modifications; and
|
||||||
|
|
||||||
|
c. indicate the Licensed Material is licensed under this
|
||||||
|
Public License, and include the text of, or the URI or
|
||||||
|
hyperlink to, this Public License.
|
||||||
|
|
||||||
|
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||||
|
reasonable manner based on the medium, means, and context in
|
||||||
|
which You Share the Licensed Material. For example, it may be
|
||||||
|
reasonable to satisfy the conditions by providing a URI or
|
||||||
|
hyperlink to a resource that includes the required
|
||||||
|
information.
|
||||||
|
|
||||||
|
3. If requested by the Licensor, You must remove any of the
|
||||||
|
information required by Section 3(a)(1)(A) to the extent
|
||||||
|
reasonably practicable.
|
||||||
|
|
||||||
|
b. ShareAlike.
|
||||||
|
|
||||||
|
In addition to the conditions in Section 3(a), if You Share
|
||||||
|
Adapted Material You produce, the following conditions also apply.
|
||||||
|
|
||||||
|
1. The Adapter's License You apply must be a Creative Commons
|
||||||
|
license with the same License Elements, this version or
|
||||||
|
later, or a BY-SA Compatible License.
|
||||||
|
|
||||||
|
2. You must include the text of, or the URI or hyperlink to, the
|
||||||
|
Adapter's License You apply. You may satisfy this condition
|
||||||
|
in any reasonable manner based on the medium, means, and
|
||||||
|
context in which You Share Adapted Material.
|
||||||
|
|
||||||
|
3. You may not offer or impose any additional or different terms
|
||||||
|
or conditions on, or apply any Effective Technological
|
||||||
|
Measures to, Adapted Material that restrict exercise of the
|
||||||
|
rights granted under the Adapter's License You apply.
|
||||||
|
|
||||||
|
|
||||||
|
Section 4 -- Sui Generis Database Rights.
|
||||||
|
|
||||||
|
Where the Licensed Rights include Sui Generis Database Rights that
|
||||||
|
apply to Your use of the Licensed Material:
|
||||||
|
|
||||||
|
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||||
|
to extract, reuse, reproduce, and Share all or a substantial
|
||||||
|
portion of the contents of the database;
|
||||||
|
|
||||||
|
b. if You include all or a substantial portion of the database
|
||||||
|
contents in a database in which You have Sui Generis Database
|
||||||
|
Rights, then the database in which You have Sui Generis Database
|
||||||
|
Rights (but not its individual contents) is Adapted Material,
|
||||||
|
|
||||||
|
including for purposes of Section 3(b); and
|
||||||
|
c. You must comply with the conditions in Section 3(a) if You Share
|
||||||
|
all or a substantial portion of the contents of the database.
|
||||||
|
|
||||||
|
For the avoidance of doubt, this Section 4 supplements and does not
|
||||||
|
replace Your obligations under this Public License where the Licensed
|
||||||
|
Rights include other Copyright and Similar Rights.
|
||||||
|
|
||||||
|
|
||||||
|
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||||
|
|
||||||
|
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||||
|
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||||
|
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||||
|
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||||
|
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||||
|
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||||
|
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||||
|
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||||
|
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||||
|
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||||
|
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||||
|
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||||
|
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||||
|
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||||
|
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||||
|
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||||
|
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
c. The disclaimer of warranties and limitation of liability provided
|
||||||
|
above shall be interpreted in a manner that, to the extent
|
||||||
|
possible, most closely approximates an absolute disclaimer and
|
||||||
|
waiver of all liability.
|
||||||
|
|
||||||
|
|
||||||
|
Section 6 -- Term and Termination.
|
||||||
|
|
||||||
|
a. This Public License applies for the term of the Copyright and
|
||||||
|
Similar Rights licensed here. However, if You fail to comply with
|
||||||
|
this Public License, then Your rights under this Public License
|
||||||
|
terminate automatically.
|
||||||
|
|
||||||
|
b. Where Your right to use the Licensed Material has terminated under
|
||||||
|
Section 6(a), it reinstates:
|
||||||
|
|
||||||
|
1. automatically as of the date the violation is cured, provided
|
||||||
|
it is cured within 30 days of Your discovery of the
|
||||||
|
violation; or
|
||||||
|
|
||||||
|
2. upon express reinstatement by the Licensor.
|
||||||
|
|
||||||
|
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||||
|
right the Licensor may have to seek remedies for Your violations
|
||||||
|
of this Public License.
|
||||||
|
|
||||||
|
c. For the avoidance of doubt, the Licensor may also offer the
|
||||||
|
Licensed Material under separate terms or conditions or stop
|
||||||
|
distributing the Licensed Material at any time; however, doing so
|
||||||
|
will not terminate this Public License.
|
||||||
|
|
||||||
|
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
|
||||||
|
Section 7 -- Other Terms and Conditions.
|
||||||
|
|
||||||
|
a. The Licensor shall not be bound by any additional or different
|
||||||
|
terms or conditions communicated by You unless expressly agreed.
|
||||||
|
|
||||||
|
b. Any arrangements, understandings, or agreements regarding the
|
||||||
|
Licensed Material not stated herein are separate from and
|
||||||
|
independent of the terms and conditions of this Public License.
|
||||||
|
|
||||||
|
|
||||||
|
Section 8 -- Interpretation.
|
||||||
|
|
||||||
|
a. For the avoidance of doubt, this Public License does not, and
|
||||||
|
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||||
|
conditions on any use of the Licensed Material that could lawfully
|
||||||
|
be made without permission under this Public License.
|
||||||
|
|
||||||
|
b. To the extent possible, if any provision of this Public License is
|
||||||
|
deemed unenforceable, it shall be automatically reformed to the
|
||||||
|
minimum extent necessary to make it enforceable. If the provision
|
||||||
|
cannot be reformed, it shall be severed from this Public License
|
||||||
|
without affecting the enforceability of the remaining terms and
|
||||||
|
conditions.
|
||||||
|
|
||||||
|
c. No term or condition of this Public License will be waived and no
|
||||||
|
failure to comply consented to unless expressly agreed to by the
|
||||||
|
Licensor.
|
||||||
|
|
||||||
|
d. Nothing in this Public License constitutes or may be interpreted
|
||||||
|
as a limitation upon, or waiver of, any privileges and immunities
|
||||||
|
that apply to the Licensor or You, including from the legal
|
||||||
|
processes of any jurisdiction or authority.
|
||||||
|
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons is not a party to its public
|
||||||
|
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||||
|
its public licenses to material it publishes and in those instances
|
||||||
|
will be considered the “Licensor.” The text of the Creative Commons
|
||||||
|
public licenses is dedicated to the public domain under the CC0 Public
|
||||||
|
Domain Dedication. Except for the limited purpose of indicating that
|
||||||
|
material is shared under a Creative Commons public license or as
|
||||||
|
otherwise permitted by the Creative Commons policies published at
|
||||||
|
creativecommons.org/policies, Creative Commons does not authorize the
|
||||||
|
use of the trademark "Creative Commons" or any other trademark or logo
|
||||||
|
of Creative Commons without its prior written consent including,
|
||||||
|
without limitation, in connection with any unauthorized modifications
|
||||||
|
to any of its public licenses or any other arrangements,
|
||||||
|
understandings, or agreements concerning use of licensed material. For
|
||||||
|
the avoidance of doubt, this paragraph does not form part of the
|
||||||
|
public licenses.
|
||||||
|
|
||||||
|
Creative Commons may be contacted at creativecommons.org.
|
||||||
|
|
||||||
92
website/public/fonts/PressStart2P/OFL.txt
Normal file
92
website/public/fonts/PressStart2P/OFL.txt
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
Copyright (c) 2012, Cody "CodeMan38" Boisclair (cody@zone38.net), with Reserved Font Name "Press Start 2P"
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
BIN
website/public/fonts/PressStart2P/PressStart2P-Regular.ttf
Normal file
BIN
website/public/fonts/PressStart2P/PressStart2P-Regular.ttf
Normal file
Binary file not shown.
|
|
@ -1,66 +1,64 @@
|
||||||
---
|
---
|
||||||
// fetch all commits for just this page's path
|
// fetch all commits for just this page's path
|
||||||
type Props = {
|
type Props = {
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
const { path } = Astro.props as Props;
|
const { path } = Astro.props as Props;
|
||||||
const resolvedPath = `examples/docs/${path}`;
|
const resolvedPath = `website/src/pages${path}.mdx`;
|
||||||
const url = `https://api.github.com/repos/withastro/astro/commits?path=${resolvedPath}`;
|
const url = `https://api.github.com/repos/tidalcycles/strudel/commits?path=${resolvedPath}`;
|
||||||
const commitsURL = `https://github.com/withastro/astro/commits/main/${resolvedPath}`;
|
const commitsURL = `https://github.com/tidalcycles/strudel/commits/main/${resolvedPath}`;
|
||||||
|
|
||||||
type Commit = {
|
type Commit = {
|
||||||
author: {
|
author: {
|
||||||
id: string;
|
id: string;
|
||||||
login: string;
|
login: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getCommits(url: string) {
|
async function getCommits(url: string) {
|
||||||
try {
|
try {
|
||||||
const token = import.meta.env.SNOWPACK_PUBLIC_GITHUB_TOKEN ?? 'hello';
|
const token = import.meta.env.SNOWPACK_PUBLIC_GITHUB_TOKEN ?? 'hello';
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Error(
|
throw new Error('Cannot find "SNOWPACK_PUBLIC_GITHUB_TOKEN" used for escaping rate-limiting.');
|
||||||
'Cannot find "SNOWPACK_PUBLIC_GITHUB_TOKEN" used for escaping rate-limiting.'
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auth = `Basic ${Buffer.from(token, 'binary').toString('base64')}`;
|
const auth = `Basic ${Buffer.from(token, 'binary').toString('base64')}`;
|
||||||
|
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: auth,
|
Authorization: auth,
|
||||||
'User-Agent': 'astro-docs/1.0',
|
'User-Agent': 'astro-docs/1.0',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Request to fetch commits failed. Reason: ${res.statusText}
|
`Request to fetch commits failed. Reason: ${res.statusText}
|
||||||
Message: ${data.message}`
|
Message: ${data.message}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return data as Commit[];
|
return data as Commit[];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(`[error] /src/components/AvatarList.astro
|
console.warn(`[error] /src/components/AvatarList.astro
|
||||||
${(e as any)?.message ?? e}`);
|
${(e as any)?.message ?? e}`);
|
||||||
return [] as Commit[];
|
return [] as Commit[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeDups(arr: Commit[]) {
|
function removeDups(arr: Commit[]) {
|
||||||
const map = new Map<string, Commit['author']>();
|
const map = new Map<string, Commit['author']>();
|
||||||
|
for (let item of arr) {
|
||||||
|
const author = item.author;
|
||||||
|
// Deduplicate based on author.id
|
||||||
|
//map.set(author.id, { login: author.login, id: author.id });
|
||||||
|
author && map.set(author.id, { login: author.login, id: author.id });
|
||||||
|
}
|
||||||
|
|
||||||
for (let item of arr) {
|
return [...map.values()];
|
||||||
const author = item.author;
|
|
||||||
// Deduplicate based on author.id
|
|
||||||
map.set(author.id, { login: author.login, id: author.id });
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...map.values()];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await getCommits(url);
|
const data = await getCommits(url);
|
||||||
|
|
@ -70,102 +68,102 @@ const additionalContributors = unique.length - recentContributors.length; // lis
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Thanks to @5t3ph for https://smolcss.dev/#smol-avatar-list! -->
|
<!-- Thanks to @5t3ph for https://smolcss.dev/#smol-avatar-list! -->
|
||||||
<div class="contributors">
|
<div class="contributors px-4 mb-8">
|
||||||
<ul class="avatar-list" style={`--avatar-count: ${recentContributors.length}`}>
|
<ul class="avatar-list" style={`--avatar-count: ${recentContributors.length}`}>
|
||||||
{
|
{
|
||||||
recentContributors.map((item) => (
|
recentContributors.map((item) => (
|
||||||
<li>
|
<li>
|
||||||
<a href={`https://github.com/${item.login}`}>
|
<a href={`https://github.com/${item.login}`}>
|
||||||
<img
|
<img
|
||||||
alt={`Contributor ${item.login}`}
|
alt={`Contributor ${item.login}`}
|
||||||
title={`Contributor ${item.login}`}
|
title={`Contributor ${item.login}`}
|
||||||
width="64"
|
width="64"
|
||||||
height="64"
|
height="64"
|
||||||
src={`https://avatars.githubusercontent.com/u/${item.id}`}
|
src={`https://avatars.githubusercontent.com/u/${item.id}`}
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
{
|
{
|
||||||
additionalContributors > 0 && (
|
additionalContributors > 0 && (
|
||||||
<span>
|
<span>
|
||||||
<a href={commitsURL}>{`and ${additionalContributors} additional contributor${
|
<a href={commitsURL}>{`and ${additionalContributors} additional contributor${
|
||||||
additionalContributors > 1 ? 's' : ''
|
additionalContributors > 1 ? 's' : ''
|
||||||
}.`}</a>
|
}.`}</a>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{unique.length === 0 && <a href={commitsURL}>Contributors</a>}
|
{unique.length === 0 && <a href={commitsURL}>Contributors</a>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.avatar-list {
|
.avatar-list {
|
||||||
--avatar-size: 2.5rem;
|
--avatar-size: 2.5rem;
|
||||||
--avatar-count: 3;
|
--avatar-count: 3;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
/* Default to displaying most of the avatar to
|
/* Default to displaying most of the avatar to
|
||||||
enable easier access on touch devices, ensuring
|
enable easier access on touch devices, ensuring
|
||||||
the WCAG touch target size is met or exceeded */
|
the WCAG touch target size is met or exceeded */
|
||||||
grid-template-columns: repeat(var(--avatar-count), max(44px, calc(var(--avatar-size) / 1.15)));
|
grid-template-columns: repeat(var(--avatar-count), max(44px, calc(var(--avatar-size) / 1.15)));
|
||||||
/* `padding` matches added visual dimensions of
|
/* `padding` matches added visual dimensions of
|
||||||
the `box-shadow` to help create a more accurate
|
the `box-shadow` to help create a more accurate
|
||||||
computed component size */
|
computed component size */
|
||||||
padding: 0.08em;
|
padding: 0.08em;
|
||||||
font-size: var(--avatar-size);
|
font-size: var(--avatar-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (any-hover: hover) and (any-pointer: fine) {
|
@media (any-hover: hover) and (any-pointer: fine) {
|
||||||
.avatar-list {
|
.avatar-list {
|
||||||
/* We create 1 extra cell to enable the computed
|
/* We create 1 extra cell to enable the computed
|
||||||
width to match the final visual width */
|
width to match the final visual width */
|
||||||
grid-template-columns: repeat(calc(var(--avatar-count) + 1), calc(var(--avatar-size) / 1.75));
|
grid-template-columns: repeat(calc(var(--avatar-count) + 1), calc(var(--avatar-size) / 1.75));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-list li {
|
.avatar-list li {
|
||||||
width: var(--avatar-size);
|
width: var(--avatar-size);
|
||||||
height: var(--avatar-size);
|
height: var(--avatar-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-list li:hover ~ li a,
|
.avatar-list li:hover ~ li a,
|
||||||
.avatar-list li:focus-within ~ li a {
|
.avatar-list li:focus-within ~ li a {
|
||||||
transform: translateX(33%);
|
transform: translateX(33%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-list img,
|
.avatar-list img,
|
||||||
.avatar-list a {
|
.avatar-list a {
|
||||||
display: block;
|
display: block;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-list a {
|
.avatar-list a {
|
||||||
transition: transform 180ms ease-in-out;
|
transition: transform 180ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-list img {
|
.avatar-list img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
box-shadow: 0 0 0 0.05em #fff, 0 0 0 0.08em rgba(0, 0, 0, 0.15);
|
box-shadow: 0 0 0 0.05em #fff, 0 0 0 0.08em rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-list a:focus {
|
.avatar-list a:focus {
|
||||||
outline: 2px solid transparent;
|
outline: 2px solid transparent;
|
||||||
/* Double-layer trick to work for dark and light backgrounds */
|
/* Double-layer trick to work for dark and light backgrounds */
|
||||||
box-shadow: 0 0 0 0.08em var(--theme-accent), 0 0 0 0.12em white;
|
box-shadow: 0 0 0 0.08em var(--theme-accent), 0 0 0 0.12em white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contributors {
|
.contributors {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contributors > * + * {
|
.contributors > * + * {
|
||||||
margin-left: 0.75rem;
|
margin-left: 0.75rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
---
|
|
||||||
import AvatarList from './AvatarList.astro';
|
|
||||||
type Props = {
|
|
||||||
path: string;
|
|
||||||
};
|
|
||||||
const { path } = Astro.props as Props;
|
|
||||||
---
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<AvatarList path={path} />
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
footer {
|
|
||||||
margin-top: auto;
|
|
||||||
padding: 2rem;
|
|
||||||
border-top: 3px solid var(--theme-divider);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
---
|
---
|
||||||
import { pwaInfo } from 'virtual:pwa-info';
|
import { pwaInfo } from 'virtual:pwa-info';
|
||||||
import '../styles/index.css';
|
import '../styles/index.css';
|
||||||
import { settings } from '../repl/themes.mjs';
|
|
||||||
|
|
||||||
const { BASE_URL } = import.meta.env;
|
const { BASE_URL } = import.meta.env;
|
||||||
const base = BASE_URL;
|
const base = BASE_URL;
|
||||||
|
|
||||||
const { strudelTheme } = settings;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Global Metadata -->
|
<!-- Global Metadata -->
|
||||||
|
|
@ -48,37 +45,43 @@ const { strudelTheme } = settings;
|
||||||
</style>
|
</style>
|
||||||
{pwaInfo && <Fragment set:html={pwaInfo.webManifest.linkTag} />}
|
{pwaInfo && <Fragment set:html={pwaInfo.webManifest.linkTag} />}
|
||||||
|
|
||||||
<script define:vars={{ settings, strudelTheme }} is:inline>
|
<script>
|
||||||
|
import { settings } from '../repl/themes.mjs';
|
||||||
|
import { settingsMap } from '../settings.mjs';
|
||||||
|
import { listenKeys } from 'nanostores';
|
||||||
const themeStyle = document.createElement('style');
|
const themeStyle = document.createElement('style');
|
||||||
themeStyle.id = 'strudel-theme';
|
themeStyle.id = 'strudel-theme';
|
||||||
document.head.append(themeStyle);
|
document.head.append(themeStyle);
|
||||||
function getTheme(name) {
|
function activateTheme(name) {
|
||||||
if (!settings[name]) {
|
if (!settings[name]) {
|
||||||
console.warn('theme', name, 'has no settings');
|
console.warn('theme', name, 'has no settings.. defaulting to strudelTheme settings');
|
||||||
}
|
}
|
||||||
return {
|
const themeSettings = settings[name] || settings.strudelTheme;
|
||||||
name,
|
|
||||||
settings: settings[name] || settings.strudelTheme,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function setTheme(name) {
|
|
||||||
const { settings } = getTheme(name);
|
|
||||||
// set css variables
|
// set css variables
|
||||||
themeStyle.innerHTML = `:root {
|
themeStyle.innerHTML = `:root {
|
||||||
${Object.entries(settings)
|
${Object.entries(themeSettings)
|
||||||
// important to override fallback
|
// important to override fallback
|
||||||
.map(([key, value]) => `--${key}: ${value} !important;`)
|
.map(([key, value]) => `--${key}: ${value} !important;`)
|
||||||
.join('\n')}
|
.join('\n')}
|
||||||
}`;
|
}`;
|
||||||
// tailwind dark mode
|
// tailwind dark mode
|
||||||
if (settings.light) {
|
if (themeSettings.light) {
|
||||||
document.documentElement.classList.remove('dark');
|
document.documentElement.classList.remove('dark');
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.classList.add('dark');
|
document.documentElement.classList.add('dark');
|
||||||
}
|
}
|
||||||
// persist theme name
|
|
||||||
localStorage.setItem('strudel-theme', name || 'strudelTheme');
|
|
||||||
}
|
}
|
||||||
setTheme(localStorage.getItem('strudel-theme'));
|
|
||||||
document.addEventListener('strudel-theme', (e) => setTheme(e.detail));
|
activateTheme(settingsMap.get().theme);
|
||||||
|
listenKeys(settingsMap, ['theme'], ({ theme }) => activateTheme(theme));
|
||||||
|
|
||||||
|
// https://medium.com/quick-code/100vh-problem-with-ios-safari-92ab23c852a8
|
||||||
|
const appHeight = () => {
|
||||||
|
const doc = document.documentElement;
|
||||||
|
doc.style.setProperty('--app-height', `${window.innerHeight}px`);
|
||||||
|
};
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('resize', appHeight);
|
||||||
|
appHeight();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import TableOfContents from './TableOfContents';
|
import TableOfContents from './TableOfContents';
|
||||||
import MoreMenu from './MoreMenu.astro';
|
import MoreMenu from './MoreMenu.astro';
|
||||||
import type { MarkdownHeading } from 'astro';
|
import type { MarkdownHeading } from 'astro';
|
||||||
|
import AvatarList from '../Footer/AvatarList.astro';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
headings: MarkdownHeading[];
|
headings: MarkdownHeading[];
|
||||||
|
|
@ -17,4 +18,5 @@ currentPage = currentPage.endsWith('/') ? currentPage.slice(0, -1) : currentPage
|
||||||
<nav aria-labelledby="grid-right" class="w-64 text-foreground">
|
<nav aria-labelledby="grid-right" class="w-64 text-foreground">
|
||||||
<TableOfContents client:media="(min-width: 50em)" headings={headings} currentPage={currentPage} />
|
<TableOfContents client:media="(min-width: 50em)" headings={headings} currentPage={currentPage} />
|
||||||
<MoreMenu editHref={githubEditUrl} />
|
<MoreMenu editHref={githubEditUrl} />
|
||||||
|
<!-- <AvatarList path={currentPage} /> -->
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@ import { useEffect, useState } from 'react';
|
||||||
import { prebake } from '../repl/prebake';
|
import { prebake } from '../repl/prebake';
|
||||||
import { themes, settings } from '../repl/themes.mjs';
|
import { themes, settings } from '../repl/themes.mjs';
|
||||||
import './MiniRepl.css';
|
import './MiniRepl.css';
|
||||||
|
import { useSettings } from '../settings.mjs';
|
||||||
const theme = localStorage.getItem('strudel-theme') || 'strudelTheme';
|
|
||||||
|
|
||||||
let modules;
|
let modules;
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
|
|
@ -29,6 +28,7 @@ if (typeof window !== 'undefined') {
|
||||||
|
|
||||||
export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
|
export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
|
||||||
const [Repl, setRepl] = useState();
|
const [Repl, setRepl] = useState();
|
||||||
|
const { theme } = useSettings();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// we have to load this package on the client
|
// we have to load this package on the client
|
||||||
// because codemirror throws an error on the server
|
// because codemirror throws an error on the server
|
||||||
|
|
@ -36,7 +36,6 @@ export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
|
||||||
.then(([res]) => setRepl(() => res.MiniRepl))
|
.then(([res]) => setRepl(() => res.MiniRepl))
|
||||||
.catch((err) => console.error(err));
|
.catch((err) => console.error(err));
|
||||||
}, []);
|
}, []);
|
||||||
// const { settings } = useTheme();
|
|
||||||
return Repl ? (
|
return Repl ? (
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<Repl
|
<Repl
|
||||||
|
|
@ -46,7 +45,6 @@ export function MiniRepl({ tune, drawTime, punchcard, canvasHeight = 100 }) {
|
||||||
punchcard={punchcard}
|
punchcard={punchcard}
|
||||||
canvasHeight={canvasHeight}
|
canvasHeight={canvasHeight}
|
||||||
theme={themes[theme]}
|
theme={themes[theme]}
|
||||||
highlightColor={settings[theme]?.foreground}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import LeftSidebar from '../components/LeftSidebar/LeftSidebar.astro';
|
||||||
import RightSidebar from '../components/RightSidebar/RightSidebar.astro';
|
import RightSidebar from '../components/RightSidebar/RightSidebar.astro';
|
||||||
import * as CONFIG from '../config';
|
import * as CONFIG from '../config';
|
||||||
import type { MarkdownHeading } from 'astro';
|
import type { MarkdownHeading } from 'astro';
|
||||||
import Footer from '../components/Footer/Footer.astro';
|
|
||||||
import '../docs/docs.css';
|
import '../docs/docs.css';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -31,7 +30,7 @@ const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
|
||||||
</title>
|
</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="h-screen text-gray-50 bg-background">
|
<body class="h-app-height text-gray-50 bg-background">
|
||||||
<div class="w-full h-full space-y-4 flex flex-col">
|
<div class="w-full h-full space-y-4 flex flex-col">
|
||||||
<header class="max-w-full fixed top-0 w-full z-[100]">
|
<header class="max-w-full fixed top-0 w-full z-[100]">
|
||||||
<Header currentPage={currentPage} />
|
<Header currentPage={currentPage} />
|
||||||
|
|
@ -49,7 +48,6 @@ const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<!-- <Footer path={currentFile} /> -->
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { Repl } from '../repl/Repl.jsx';
|
||||||
<HeadCommon />
|
<HeadCommon />
|
||||||
<title>Strudel REPL</title>
|
<title>Strudel REPL</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-background">
|
<body class="h-app-height bg-background">
|
||||||
<Repl client:only="react" />
|
<Repl client:only="react" />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ Let's look at this simple example again. What do we notice?
|
||||||
|
|
||||||
<MiniRepl client:idle tune={`freq("220 275 330 440").s("sine")`} />
|
<MiniRepl client:idle tune={`freq("220 275 330 440").s("sine")`} />
|
||||||
|
|
||||||
- We have a word `freq` which is followed by some brackets `()` with some words/letters/numbers inside, surrounded by quotes `"a3 c#4 e4 a4"`.
|
- We have a word `freq` which is followed by some brackets `()` with some words/letters/numbers inside, surrounded by quotes `"220 275 330 440"` (corresponding to the pitches a3, c#4, e4, a4).
|
||||||
- Then we have a dot `.` followed by another similar piece of code `s("sine")`.
|
- Then we have a dot `.` followed by another similar piece of code `s("sine")`.
|
||||||
- We can also see these texts are _highlighted_ using colours: word `freq` is purple, the brackets `()` are grey, and the content inside the `""` are green.
|
- We can also see these texts are _highlighted_ using colours: word `freq` is purple, the brackets `()` are grey, and the content inside the `""` are green.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,35 @@ If no outputName is given, it uses the first midi output it finds.
|
||||||
|
|
||||||
<MiniRepl
|
<MiniRepl
|
||||||
client:idle
|
client:idle
|
||||||
tune={`stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>")
|
tune={`stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>").note()
|
||||||
.midi()`}
|
.midi()`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
In the console, you will see a log of the available MIDI devices as soon as you run the code, e.g. `Midi connected! Using "Midi Through Port-0".`
|
In the console, you will see a log of the available MIDI devices as soon as you run the code, e.g. `Midi connected! Using "Midi Through Port-0".`
|
||||||
|
|
||||||
|
## midichan(number)
|
||||||
|
|
||||||
|
Selects the MIDI channel to use. If not used, `.midi` will use channel 1 by default.
|
||||||
|
|
||||||
|
## ccn && ccv
|
||||||
|
|
||||||
|
- `ccn` sets the cc number. Depends on your synths midi mapping
|
||||||
|
- `ccv` sets the cc value. normalized from 0 to 1.
|
||||||
|
|
||||||
|
<MiniRepl client:idle tune={`note("c a f e").ccn(74).ccv(sine.slow(4)).midi()`} />
|
||||||
|
|
||||||
|
In the above snippet, `ccn` is set to 74, which is the filter cutoff for many synths. `ccv` is controlled by a saw pattern.
|
||||||
|
Having everything in one pattern, the `ccv` pattern will be aligned to the note pattern, because the structure comes from the left by default.
|
||||||
|
But you can also control cc messages separately like this:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:idle
|
||||||
|
tune={`stack(
|
||||||
|
note("c a f e"),
|
||||||
|
ccv(sine.segment(16).slow(4)).ccn(74)
|
||||||
|
).midi()`}
|
||||||
|
/>
|
||||||
|
|
||||||
# SuperDirt API
|
# SuperDirt API
|
||||||
|
|
||||||
In mainline tidal, the actual sound is generated via [SuperDirt](https://github.com/musikinformatik/SuperDirt/), which runs inside SuperCollider.
|
In mainline tidal, the actual sound is generated via [SuperDirt](https://github.com/musikinformatik/SuperDirt/), which runs inside SuperCollider.
|
||||||
|
|
@ -40,8 +63,8 @@ Getting [SuperDirt](https://github.com/musikinformatik/SuperDirt/) to work with
|
||||||
1. install SuperCollider + sc3 plugins, see [Tidal Docs](https://tidalcycles.org/docs/) (Install Tidal) for more info.
|
1. install SuperCollider + sc3 plugins, see [Tidal Docs](https://tidalcycles.org/docs/) (Install Tidal) for more info.
|
||||||
2. install [node.js](https://nodejs.org/en/)
|
2. install [node.js](https://nodejs.org/en/)
|
||||||
3. download [Strudel Repo](https://github.com/tidalcycles/strudel/) (or git clone, if you have git installed)
|
3. download [Strudel Repo](https://github.com/tidalcycles/strudel/) (or git clone, if you have git installed)
|
||||||
4. run `npm i` in the strudel directory
|
4. run `pnpm i` in the strudel directory
|
||||||
5. run `npm run osc` to start the osc server, which forwards OSC messages from Strudel REPL to SuperCollider
|
5. run `pnpm run osc` to start the osc server, which forwards OSC messages from Strudel REPL to SuperCollider
|
||||||
|
|
||||||
Now you're all set!
|
Now you're all set!
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,13 @@ import { nanoid } from 'nanoid';
|
||||||
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { loadedSamples } from './Repl';
|
import { loadedSamples } from './Repl';
|
||||||
import { Reference } from './Reference';
|
import { Reference } from './Reference';
|
||||||
import { themes, themeColors } from './themes.mjs';
|
import { themes } from './themes.mjs';
|
||||||
|
import { useSettings, settingsMap, setActiveFooter, defaultSettings } from '../settings.mjs';
|
||||||
|
|
||||||
export function Footer({ context }) {
|
export function Footer({ context }) {
|
||||||
// const [activeFooter, setActiveFooter] = useState('console');
|
|
||||||
// const { activeFooter, setActiveFooter, isZen } = useContext?.(ReplContext);
|
|
||||||
const { activeFooter, setActiveFooter, isZen, theme, setTheme } = context;
|
|
||||||
const footerContent = useRef();
|
const footerContent = useRef();
|
||||||
const [log, setLog] = useState([]);
|
const [log, setLog] = useState([]);
|
||||||
|
const { activeFooter, isZen } = useSettings();
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (footerContent.current && activeFooter === 'console') {
|
if (footerContent.current && activeFooter === 'console') {
|
||||||
|
|
@ -56,8 +55,8 @@ export function Footer({ context }) {
|
||||||
<div
|
<div
|
||||||
onClick={() => setActiveFooter(name)}
|
onClick={() => setActiveFooter(name)}
|
||||||
className={cx(
|
className={cx(
|
||||||
'h-8 px-2 text-foreground cursor-pointer hover:text-tertiary flex items-center space-x-1 border-b',
|
'h-8 px-2 text-foreground cursor-pointer hover:opacity-50 flex items-center space-x-1 border-b',
|
||||||
activeFooter === name ? 'border-foreground hover:border-tertiary' : 'border-transparent',
|
activeFooter === name ? 'border-foreground' : 'border-transparent',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{label || name}
|
{label || name}
|
||||||
|
|
@ -76,7 +75,7 @@ export function Footer({ context }) {
|
||||||
<FooterTab name="samples" />
|
<FooterTab name="samples" />
|
||||||
<FooterTab name="console" />
|
<FooterTab name="console" />
|
||||||
<FooterTab name="reference" />
|
<FooterTab name="reference" />
|
||||||
<FooterTab name="theme" />
|
<FooterTab name="settings" />
|
||||||
</div>
|
</div>
|
||||||
{activeFooter !== '' && (
|
{activeFooter !== '' && (
|
||||||
<button onClick={() => setActiveFooter('')} className="text-foreground" aria-label="Close Panel">
|
<button onClick={() => setActiveFooter('')} className="text-foreground" aria-label="Close Panel">
|
||||||
|
|
@ -89,113 +88,11 @@ export function Footer({ context }) {
|
||||||
className="text-white font-mono text-sm h-[360px] flex-none overflow-auto max-w-full relative"
|
className="text-white font-mono text-sm h-[360px] flex-none overflow-auto max-w-full relative"
|
||||||
ref={footerContent}
|
ref={footerContent}
|
||||||
>
|
>
|
||||||
{activeFooter === 'intro' && (
|
{activeFooter === 'intro' && <WelcomeTab />}
|
||||||
<div className="prose dark:prose-invert max-w-[600px] pt-2 font-sans pb-8 px-4">
|
{activeFooter === 'console' && <ConsoleTab log={log} />}
|
||||||
<h3>
|
{activeFooter === 'samples' && <SamplesTab />}
|
||||||
<span className={cx('animate-spin inline-block select-none')}>🌀</span> welcome
|
|
||||||
</h3>
|
|
||||||
<p>
|
|
||||||
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:
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<span className="underline">1. hit play</span> - <span className="underline">2. change something</span>{' '}
|
|
||||||
- <span className="underline">3. hit update</span>
|
|
||||||
<br />
|
|
||||||
If you don't like what you hear, try <span className="underline">shuffle</span>!
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
To learn more about what this all means, check out the{' '}
|
|
||||||
<a href="./learn/getting-started" target="_blank">
|
|
||||||
interactive tutorial
|
|
||||||
</a>
|
|
||||||
. Also feel free to join the{' '}
|
|
||||||
<a href="https://discord.com/invite/HGEdXmRkzT" target="_blank">
|
|
||||||
tidalcycles discord channel
|
|
||||||
</a>{' '}
|
|
||||||
to ask any questions, give feedback or just say hello.
|
|
||||||
</p>
|
|
||||||
<h3>about</h3>
|
|
||||||
<p>
|
|
||||||
strudel is a JavaScript version of{' '}
|
|
||||||
<a href="tidalcycles.org/" target="_blank">
|
|
||||||
tidalcycles
|
|
||||||
</a>
|
|
||||||
, which is a popular live coding language for music, written in Haskell. You can find the source code at{' '}
|
|
||||||
<a href="https://github.com/tidalcycles/strudel" target="_blank">
|
|
||||||
github
|
|
||||||
</a>
|
|
||||||
. Please consider to{' '}
|
|
||||||
<a href="https://opencollective.com/tidalcycles" target="_blank">
|
|
||||||
support this project
|
|
||||||
</a>{' '}
|
|
||||||
to ensure ongoing development 💖
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{activeFooter === 'console' && (
|
|
||||||
<div className="break-all px-4 dark:text-white text-stone-900">
|
|
||||||
{log.map((l, i) => {
|
|
||||||
const message = linkify(l.message);
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={l.id}
|
|
||||||
className={cx(l.type === 'error' && 'text-red-500', l.type === 'highlight' && 'text-highlight')}
|
|
||||||
>
|
|
||||||
<span dangerouslySetInnerHTML={{ __html: message }} />
|
|
||||||
{l.count ? ` (${l.count})` : ''}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{activeFooter === 'samples' && (
|
|
||||||
<div className="break-normal w-full px-4 dark:text-white text-stone-900">
|
|
||||||
<span>{loadedSamples.length} banks loaded:</span>
|
|
||||||
{loadedSamples.map(([name, samples]) => (
|
|
||||||
<span key={name} className="cursor-pointer hover:text-tertiary" onClick={() => {}}>
|
|
||||||
{' '}
|
|
||||||
{name}(
|
|
||||||
{Array.isArray(samples)
|
|
||||||
? samples.length
|
|
||||||
: typeof samples === 'object'
|
|
||||||
? Object.values(samples).length
|
|
||||||
: 1}
|
|
||||||
){' '}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{activeFooter === 'reference' && <Reference />}
|
{activeFooter === 'reference' && <Reference />}
|
||||||
{activeFooter === 'theme' && (
|
{activeFooter === 'settings' && <SettingsTab />}
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-2 p-2">
|
|
||||||
{Object.entries(themes).map(([k, t]) => (
|
|
||||||
<div
|
|
||||||
key={k}
|
|
||||||
className={cx(
|
|
||||||
'border-2 border-transparent cursor-pointer p-4 bg-background bg-opacity-25 rounded-md',
|
|
||||||
theme === k ? '!border-foreground' : '',
|
|
||||||
)}
|
|
||||||
onClick={() => {
|
|
||||||
setTheme(k);
|
|
||||||
document.dispatchEvent(
|
|
||||||
new CustomEvent('strudel-theme', {
|
|
||||||
detail: k,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="mb-2 w-full text-center text-foreground">{k}</div>
|
|
||||||
<div className="flex justify-stretch overflow-hidden rounded-md">
|
|
||||||
{themeColors(t).map((c, i) => (
|
|
||||||
<div key={i} className="grow h-6" style={{ background: c }} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</footer>
|
</footer>
|
||||||
|
|
@ -226,3 +123,211 @@ function linkify(inputText) {
|
||||||
|
|
||||||
return replacedText;
|
return replacedText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function WelcomeTab() {
|
||||||
|
return (
|
||||||
|
<div className="prose dark:prose-invert max-w-[600px] pt-2 font-sans pb-8 px-4">
|
||||||
|
<h3>
|
||||||
|
<span className={cx('animate-spin inline-block select-none')}>🌀</span> welcome
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
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:
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<span className="underline">1. hit play</span> - <span className="underline">2. change something</span> -{' '}
|
||||||
|
<span className="underline">3. hit update</span>
|
||||||
|
<br />
|
||||||
|
If you don't like what you hear, try <span className="underline">shuffle</span>!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To learn more about what this all means, check out the{' '}
|
||||||
|
<a href="./learn/getting-started" target="_blank">
|
||||||
|
interactive tutorial
|
||||||
|
</a>
|
||||||
|
. Also feel free to join the{' '}
|
||||||
|
<a href="https://discord.com/invite/HGEdXmRkzT" target="_blank">
|
||||||
|
tidalcycles discord channel
|
||||||
|
</a>{' '}
|
||||||
|
to ask any questions, give feedback or just say hello.
|
||||||
|
</p>
|
||||||
|
<h3>about</h3>
|
||||||
|
<p>
|
||||||
|
strudel is a JavaScript version of{' '}
|
||||||
|
<a href="tidalcycles.org/" target="_blank">
|
||||||
|
tidalcycles
|
||||||
|
</a>
|
||||||
|
, which is a popular live coding language for music, written in Haskell. You can find the source code at{' '}
|
||||||
|
<a href="https://github.com/tidalcycles/strudel" target="_blank">
|
||||||
|
github
|
||||||
|
</a>
|
||||||
|
. Please consider to{' '}
|
||||||
|
<a href="https://opencollective.com/tidalcycles" target="_blank">
|
||||||
|
support this project
|
||||||
|
</a>{' '}
|
||||||
|
to ensure ongoing development 💖
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConsoleTab({ log }) {
|
||||||
|
return (
|
||||||
|
<div id="console-tab" className="break-all px-4 dark:text-white text-stone-900">
|
||||||
|
<pre>{`███████╗████████╗██████╗ ██╗ ██╗██████╗ ███████╗██╗
|
||||||
|
██╔════╝╚══██╔══╝██╔══██╗██║ ██║██╔══██╗██╔════╝██║
|
||||||
|
███████╗ ██║ ██████╔╝██║ ██║██║ ██║█████╗ ██║
|
||||||
|
╚════██║ ██║ ██╔══██╗██║ ██║██║ ██║██╔══╝ ██║
|
||||||
|
███████║ ██║ ██║ ██║╚██████╔╝██████╔╝███████╗███████╗
|
||||||
|
╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝╚══════╝`}</pre>
|
||||||
|
{log.map((l, i) => {
|
||||||
|
const message = linkify(l.message);
|
||||||
|
return (
|
||||||
|
<div key={l.id} className={cx(l.type === 'error' && 'text-red-500', l.type === 'highlight' && 'underline')}>
|
||||||
|
<span dangerouslySetInnerHTML={{ __html: message }} />
|
||||||
|
{l.count ? ` (${l.count})` : ''}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SamplesTab() {
|
||||||
|
return (
|
||||||
|
<div id="samples-tab" className="break-normal w-full px-4 dark:text-white text-stone-900">
|
||||||
|
<span>{loadedSamples.length} banks loaded:</span>
|
||||||
|
{loadedSamples.map(([name, samples]) => (
|
||||||
|
<span key={name} className="cursor-pointer hover:opacity-50" onClick={() => {}}>
|
||||||
|
{' '}
|
||||||
|
{name}(
|
||||||
|
{Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1}){' '}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ButtonGroup({ value, onChange, items }) {
|
||||||
|
return (
|
||||||
|
<div className="flex grow border border-foreground rounded-md">
|
||||||
|
{Object.entries(items).map(([key, label], i, arr) => (
|
||||||
|
<button
|
||||||
|
key={key}
|
||||||
|
onClick={() => onChange(key)}
|
||||||
|
className={cx(
|
||||||
|
'p-2 grow',
|
||||||
|
i === 0 && 'rounded-l-md',
|
||||||
|
i === arr.length - 1 && 'rounded-r-md',
|
||||||
|
value === key ? 'bg-background' : 'bg-lineHighlight',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectInput({ value, options, onChange }) {
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
className="p-2 bg-background rounded-md text-foreground"
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
>
|
||||||
|
{Object.entries(options).map(([k, label]) => (
|
||||||
|
<option key={k} className="bg-background" value={k}>
|
||||||
|
{label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NumberSlider({ value, onChange, step = 1, ...rest }) {
|
||||||
|
return (
|
||||||
|
<div className="flex space-x-2 gap-1">
|
||||||
|
<input
|
||||||
|
className="p-2 grow"
|
||||||
|
type="range"
|
||||||
|
value={value}
|
||||||
|
step={step}
|
||||||
|
onChange={(e) => onChange(Number(e.target.value))}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={value}
|
||||||
|
step={step}
|
||||||
|
className="w-16 bg-background rounded-md"
|
||||||
|
onChange={(e) => onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FormItem({ label, children }) {
|
||||||
|
return (
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<label>{label}</label>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeOptions = Object.fromEntries(Object.keys(themes).map((k) => [k, k]));
|
||||||
|
const fontFamilyOptions = {
|
||||||
|
monospace: 'monospace',
|
||||||
|
BigBlueTerminal: 'BigBlueTerminal',
|
||||||
|
x3270: 'x3270',
|
||||||
|
PressStart: 'PressStart2P',
|
||||||
|
};
|
||||||
|
|
||||||
|
function SettingsTab() {
|
||||||
|
const { theme, keybindings, fontSize, fontFamily } = useSettings();
|
||||||
|
return (
|
||||||
|
<div className="text-foreground p-4 space-y-4">
|
||||||
|
<FormItem label="Theme">
|
||||||
|
<SelectInput options={themeOptions} value={theme} onChange={(theme) => settingsMap.setKey('theme', theme)} />
|
||||||
|
</FormItem>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<FormItem label="Font Family">
|
||||||
|
<SelectInput
|
||||||
|
options={fontFamilyOptions}
|
||||||
|
value={fontFamily}
|
||||||
|
onChange={(fontFamily) => settingsMap.setKey('fontFamily', fontFamily)}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="Font Size">
|
||||||
|
<NumberSlider
|
||||||
|
value={fontSize}
|
||||||
|
onChange={(fontSize) => settingsMap.setKey('fontSize', fontSize)}
|
||||||
|
min={10}
|
||||||
|
max={40}
|
||||||
|
step={2}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
<FormItem label="Keybindings">
|
||||||
|
<ButtonGroup
|
||||||
|
value={keybindings}
|
||||||
|
onChange={(keybindings) => settingsMap.setKey('keybindings', keybindings)}
|
||||||
|
items={{ codemirror: 'Codemirror', vim: 'Vim', emacs: 'Emacs' }}
|
||||||
|
></ButtonGroup>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="Reset Settings">
|
||||||
|
<button
|
||||||
|
className="bg-background p-2 max-w-[300px] rounded-md hover:opacity-50"
|
||||||
|
onClick={() => {
|
||||||
|
if (confirm('Sure?')) {
|
||||||
|
settingsMap.set(defaultSettings);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
restore default settings
|
||||||
|
</button>
|
||||||
|
</FormItem>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import SparklesIcon from '@heroicons/react/20/solid/SparklesIcon';
|
||||||
import StopCircleIcon from '@heroicons/react/20/solid/StopCircleIcon';
|
import StopCircleIcon from '@heroicons/react/20/solid/StopCircleIcon';
|
||||||
import { cx } from '@strudel.cycles/react';
|
import { cx } from '@strudel.cycles/react';
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
|
import { useSettings, setIsZen } from '../settings.mjs';
|
||||||
// import { ReplContext } from './Repl';
|
// import { ReplContext } from './Repl';
|
||||||
import './Repl.css';
|
import './Repl.css';
|
||||||
|
|
||||||
|
|
@ -21,11 +22,9 @@ export function Header({ context }) {
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
handleShuffle,
|
handleShuffle,
|
||||||
handleShare,
|
handleShare,
|
||||||
isZen,
|
|
||||||
setIsZen,
|
|
||||||
} = context;
|
} = context;
|
||||||
const isEmbedded = embedded || window.location !== window.parent.location;
|
const isEmbedded = embedded || window.location !== window.parent.location;
|
||||||
// useContext(ReplContext)
|
const { isZen } = useSettings();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
|
|
@ -53,7 +52,11 @@ export function Header({ context }) {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={cx('mt-[1px]', started && 'animate-spin', 'cursor-pointer')}
|
className={cx('mt-[1px]', started && 'animate-spin', 'cursor-pointer')}
|
||||||
onClick={() => !isEmbedded && setIsZen((z) => !z)}
|
onClick={() => {
|
||||||
|
if (!isEmbedded) {
|
||||||
|
setIsZen(!isZen);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
🌀
|
🌀
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -69,7 +72,7 @@ export function Header({ context }) {
|
||||||
<button
|
<button
|
||||||
onClick={handleTogglePlay}
|
onClick={handleTogglePlay}
|
||||||
title={started ? 'stop' : 'play'}
|
title={started ? 'stop' : 'play'}
|
||||||
className={cx(!isEmbedded ? 'p-2' : 'px-2', 'hover:text-tertiary', !started && 'animate-pulse')}
|
className={cx(!isEmbedded ? 'p-2' : 'px-2', 'hover:opacity-50', !started && 'animate-pulse')}
|
||||||
>
|
>
|
||||||
{!pending ? (
|
{!pending ? (
|
||||||
<span className={cx('flex items-center space-x-1', isEmbedded ? '' : 'w-16')}>
|
<span className={cx('flex items-center space-x-1', isEmbedded ? '' : 'w-16')}>
|
||||||
|
|
@ -86,7 +89,7 @@ export function Header({ context }) {
|
||||||
className={cx(
|
className={cx(
|
||||||
'flex items-center space-x-1',
|
'flex items-center space-x-1',
|
||||||
!isEmbedded ? 'p-2' : 'px-2',
|
!isEmbedded ? 'p-2' : 'px-2',
|
||||||
!isDirty || !activeCode ? 'opacity-50' : 'hover:text-tertiary',
|
!isDirty || !activeCode ? 'opacity-50' : 'hover:opacity-50',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* <CommandLineIcon className="w-6 h-6" /> */}
|
{/* <CommandLineIcon className="w-6 h-6" /> */}
|
||||||
|
|
@ -96,7 +99,7 @@ export function Header({ context }) {
|
||||||
{!isEmbedded && (
|
{!isEmbedded && (
|
||||||
<button
|
<button
|
||||||
title="shuffle"
|
title="shuffle"
|
||||||
className="hover:text-tertiary p-2 flex items-center space-x-1"
|
className="hover:opacity-50 p-2 flex items-center space-x-1"
|
||||||
onClick={handleShuffle}
|
onClick={handleShuffle}
|
||||||
>
|
>
|
||||||
<SparklesIcon className="w-6 h-6" />
|
<SparklesIcon className="w-6 h-6" />
|
||||||
|
|
@ -107,7 +110,7 @@ export function Header({ context }) {
|
||||||
<button
|
<button
|
||||||
title="share"
|
title="share"
|
||||||
className={cx(
|
className={cx(
|
||||||
'cursor-pointer hover:text-tertiary flex items-center space-x-1',
|
'cursor-pointer hover:opacity-50 flex items-center space-x-1',
|
||||||
!isEmbedded ? 'p-2' : 'px-2',
|
!isEmbedded ? 'p-2' : 'px-2',
|
||||||
)}
|
)}
|
||||||
onClick={handleShare}
|
onClick={handleShare}
|
||||||
|
|
@ -120,21 +123,21 @@ export function Header({ context }) {
|
||||||
<a
|
<a
|
||||||
title="learn"
|
title="learn"
|
||||||
href="./learn/getting-started"
|
href="./learn/getting-started"
|
||||||
className={cx('hover:text-tertiary flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
|
className={cx('hover:opacity-50 flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
|
||||||
>
|
>
|
||||||
<AcademicCapIcon className="w-6 h-6" />
|
<AcademicCapIcon className="w-6 h-6" />
|
||||||
<span>learn</span>
|
<span>learn</span>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{/* {isEmbedded && (
|
{/* {isEmbedded && (
|
||||||
<button className={cx('hover:text-tertiary px-2')}>
|
<button className={cx('hover:opacity-50 px-2')}>
|
||||||
<a href={window.location.href} target="_blank" rel="noopener noreferrer" title="Open in REPL">
|
<a href={window.location.href} target="_blank" rel="noopener noreferrer" title="Open in REPL">
|
||||||
🚀
|
🚀
|
||||||
</a>
|
</a>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{isEmbedded && (
|
{isEmbedded && (
|
||||||
<button className={cx('hover:text-tertiary px-2')}>
|
<button className={cx('hover:opacity-50 px-2')}>
|
||||||
<a
|
<a
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.location.href = initialUrl;
|
window.location.href = initialUrl;
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
#code .cm-scroller {
|
#code .cm-scroller {
|
||||||
padding-bottom: 50vh;
|
padding-bottom: 50vh;
|
||||||
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
#code .cm-line > * {
|
#code .cm-line > * {
|
||||||
background: var(--lineBackground);
|
background: var(--lineBackground);
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ import { prebake } from './prebake.mjs';
|
||||||
import * as tunes from './tunes.mjs';
|
import * as tunes from './tunes.mjs';
|
||||||
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
||||||
import { themes } from './themes.mjs';
|
import { themes } from './themes.mjs';
|
||||||
import useTheme from '../useTheme';
|
import { settingsMap, useSettings, setLatestCode } from '../settings.mjs';
|
||||||
|
|
||||||
const initialTheme = localStorage.getItem('strudel-theme') || 'strudelTheme';
|
const { latestCode } = settingsMap.get();
|
||||||
|
|
||||||
initAudioOnFirstClick();
|
initAudioOnFirstClick();
|
||||||
|
|
||||||
|
|
@ -113,25 +113,25 @@ export const ReplContext = createContext(null);
|
||||||
export function Repl({ embedded = false }) {
|
export function Repl({ embedded = false }) {
|
||||||
const isEmbedded = embedded || window.location !== window.parent.location;
|
const isEmbedded = embedded || window.location !== window.parent.location;
|
||||||
const [view, setView] = useState(); // codemirror view
|
const [view, setView] = useState(); // codemirror view
|
||||||
const [theme, setTheme] = useState(initialTheme);
|
|
||||||
const [lastShared, setLastShared] = useState();
|
const [lastShared, setLastShared] = useState();
|
||||||
const [activeFooter, setActiveFooter] = useState('');
|
|
||||||
const [isZen, setIsZen] = useState(false);
|
|
||||||
const [pending, setPending] = useState(false);
|
const [pending, setPending] = useState(false);
|
||||||
|
|
||||||
|
const { theme, keybindings, fontSize, fontFamily } = useSettings();
|
||||||
|
|
||||||
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
|
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
|
||||||
useStrudel({
|
useStrudel({
|
||||||
initialCode: '// LOADING',
|
initialCode: '// LOADING',
|
||||||
defaultOutput: webaudioOutput,
|
defaultOutput: webaudioOutput,
|
||||||
getTime,
|
getTime,
|
||||||
autolink: true,
|
|
||||||
beforeEval: () => {
|
beforeEval: () => {
|
||||||
cleanupUi();
|
cleanupUi();
|
||||||
cleanupDraw();
|
cleanupDraw();
|
||||||
setPending(true);
|
setPending(true);
|
||||||
},
|
},
|
||||||
afterEval: () => {
|
afterEval: ({ code }) => {
|
||||||
setPending(false);
|
setPending(false);
|
||||||
|
setLatestCode(code);
|
||||||
|
window.location.hash = '#' + encodeURIComponent(btoa(code));
|
||||||
},
|
},
|
||||||
onToggle: (play) => !play && cleanupDraw(false),
|
onToggle: (play) => !play && cleanupDraw(false),
|
||||||
drawContext,
|
drawContext,
|
||||||
|
|
@ -140,16 +140,13 @@ export function Repl({ embedded = false }) {
|
||||||
// init code
|
// init code
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initCode().then((decoded) => {
|
initCode().then((decoded) => {
|
||||||
if (!decoded) {
|
|
||||||
setActiveFooter('intro'); // TODO: get rid
|
|
||||||
}
|
|
||||||
logger(
|
logger(
|
||||||
`Welcome to Strudel! ${
|
`Welcome to Strudel! ${
|
||||||
decoded ? `I have loaded the code from the URL.` : `A random code snippet named "${name}" has been loaded!`
|
decoded ? `I have loaded the code from the URL.` : `A random code snippet named "${name}" has been loaded!`
|
||||||
} Press play or hit ctrl+enter to run it!`,
|
} Press play or hit ctrl+enter to run it!`,
|
||||||
'highlight',
|
'highlight',
|
||||||
);
|
);
|
||||||
setCode(decoded || randomTune);
|
setCode(decoded || latestCode || randomTune);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -172,15 +169,12 @@ export function Repl({ embedded = false }) {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { settings } = useTheme();
|
|
||||||
|
|
||||||
// highlighting
|
// highlighting
|
||||||
useHighlighting({
|
useHighlighting({
|
||||||
view,
|
view,
|
||||||
pattern,
|
pattern,
|
||||||
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
||||||
getTime: () => scheduler.now(),
|
getTime: () => scheduler.now(),
|
||||||
color: settings?.foreground,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
@ -254,17 +248,11 @@ export function Repl({ embedded = false }) {
|
||||||
isDirty,
|
isDirty,
|
||||||
lastShared,
|
lastShared,
|
||||||
activeCode,
|
activeCode,
|
||||||
activeFooter,
|
|
||||||
setActiveFooter,
|
|
||||||
handleChangeCode,
|
handleChangeCode,
|
||||||
handleTogglePlay,
|
handleTogglePlay,
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
handleShuffle,
|
handleShuffle,
|
||||||
handleShare,
|
handleShare,
|
||||||
isZen,
|
|
||||||
setIsZen,
|
|
||||||
theme,
|
|
||||||
setTheme,
|
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
// bg-gradient-to-t from-blue-900 to-slate-900
|
// bg-gradient-to-t from-blue-900 to-slate-900
|
||||||
|
|
@ -272,7 +260,7 @@ export function Repl({ embedded = false }) {
|
||||||
<ReplContext.Provider value={context}>
|
<ReplContext.Provider value={context}>
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx(
|
||||||
'h-screen flex flex-col',
|
'h-full flex flex-col',
|
||||||
// 'bg-gradient-to-t from-green-900 to-slate-900', //
|
// 'bg-gradient-to-t from-green-900 to-slate-900', //
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -281,6 +269,9 @@ export function Repl({ embedded = false }) {
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
theme={themes[theme] || themes.strudelTheme}
|
theme={themes[theme] || themes.strudelTheme}
|
||||||
value={code}
|
value={code}
|
||||||
|
keybindings={keybindings}
|
||||||
|
fontSize={fontSize}
|
||||||
|
fontFamily={fontFamily}
|
||||||
onChange={handleChangeCode}
|
onChange={handleChangeCode}
|
||||||
onViewChanged={setView}
|
onViewChanged={setView}
|
||||||
onSelectionChange={handleSelectionChange}
|
onSelectionChange={handleSelectionChange}
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,19 @@ import {
|
||||||
} from '@uiw/codemirror-themes-all';
|
} from '@uiw/codemirror-themes-all';
|
||||||
|
|
||||||
import strudelTheme from '@strudel.cycles/react/src/themes/strudel-theme';
|
import strudelTheme from '@strudel.cycles/react/src/themes/strudel-theme';
|
||||||
|
import bluescreen, { settings as bluescreenSettings } from '@strudel.cycles/react/src/themes/bluescreen';
|
||||||
|
import blackscreen, { settings as blackscreenSettings } from '@strudel.cycles/react/src/themes/blackscreen';
|
||||||
|
import whitescreen, { settings as whitescreenSettings } from '@strudel.cycles/react/src/themes/whitescreen';
|
||||||
|
import algoboy, { settings as algoboySettings } from '@strudel.cycles/react/src/themes/algoboy';
|
||||||
|
import terminal, { settings as terminalSettings } from '@strudel.cycles/react/src/themes/terminal';
|
||||||
|
|
||||||
export const themes = {
|
export const themes = {
|
||||||
strudelTheme,
|
strudelTheme,
|
||||||
|
bluescreen,
|
||||||
|
blackscreen,
|
||||||
|
whitescreen,
|
||||||
|
algoboy,
|
||||||
|
terminal,
|
||||||
abcdef,
|
abcdef,
|
||||||
androidstudio,
|
androidstudio,
|
||||||
atomone,
|
atomone,
|
||||||
|
|
@ -82,6 +92,11 @@ export const settings = {
|
||||||
// gutterForeground: '#8a919966',
|
// gutterForeground: '#8a919966',
|
||||||
gutterForeground: '#8a919966',
|
gutterForeground: '#8a919966',
|
||||||
},
|
},
|
||||||
|
bluescreen: bluescreenSettings,
|
||||||
|
blackscreen: blackscreenSettings,
|
||||||
|
whitescreen: whitescreenSettings,
|
||||||
|
algoboy: algoboySettings,
|
||||||
|
terminal: terminalSettings,
|
||||||
abcdef: {
|
abcdef: {
|
||||||
background: '#0f0f0f',
|
background: '#0f0f0f',
|
||||||
lineBackground: '#0f0f0f50',
|
lineBackground: '#0f0f0f50',
|
||||||
|
|
@ -395,7 +410,6 @@ export const settings = {
|
||||||
gutterBackground: '#1e1e1e',
|
gutterBackground: '#1e1e1e',
|
||||||
gutterForeground: '#838383',
|
gutterForeground: '#838383',
|
||||||
gutterActiveForeground: '#fff',
|
gutterActiveForeground: '#fff',
|
||||||
fontFamily: 'Menlo, Monaco, Consolas, "Andale Mono", "Ubuntu Mono", "Courier New", monospace',
|
|
||||||
},
|
},
|
||||||
xcodeLight: {
|
xcodeLight: {
|
||||||
light: true,
|
light: true,
|
||||||
|
|
|
||||||
28
website/src/settings.mjs
Normal file
28
website/src/settings.mjs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { persistentMap } from '@nanostores/persistent';
|
||||||
|
import { useStore } from '@nanostores/react';
|
||||||
|
|
||||||
|
export const defaultSettings = {
|
||||||
|
activeFooter: 'intro',
|
||||||
|
keybindings: 'codemirror',
|
||||||
|
theme: 'strudelTheme',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: 18,
|
||||||
|
latestCode: '',
|
||||||
|
isZen: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const settingsMap = persistentMap('strudel-settings', defaultSettings);
|
||||||
|
|
||||||
|
export function useSettings() {
|
||||||
|
const state = useStore(settingsMap);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isZen: [true, 'true'].includes(state.isZen) ? true : false,
|
||||||
|
fontSize: Number(state.fontSize),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
|
||||||
|
|
||||||
|
export const setLatestCode = (code) => settingsMap.setKey('latestCode', code);
|
||||||
|
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
|
||||||
|
|
@ -1,3 +1,16 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'PressStart';
|
||||||
|
src: url('/fonts/PressStart2P/PressStart2P-Regular.ttf');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'BigBlueTerminal';
|
||||||
|
src: url('/fonts/BigBlueTerminal/BigBlue_TerminalPlus.TTF');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'x3270';
|
||||||
|
src: url('/fonts/3270/3270-Regular.ttf');
|
||||||
|
}
|
||||||
|
|
||||||
.cm-gutters {
|
.cm-gutters {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
@ -5,3 +18,11 @@
|
||||||
.prose > h1:not(:first-child) {
|
.prose > h1:not(:first-child) {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
:root {
|
||||||
|
--app-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#console-tab,
|
||||||
|
#samples-tab {
|
||||||
|
font-family: BigBlueTerminal, monospace;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
import { useState } from 'react';
|
|
||||||
import { settings } from './repl/themes.mjs';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
function useTheme() {
|
|
||||||
const [theme, setTheme] = useState(localStorage.getItem('strudel-theme'));
|
|
||||||
useEvent('strudel-theme', (e) => setTheme(e.detail));
|
|
||||||
const themeSettings = settings[theme || 'strudelTheme'];
|
|
||||||
return {
|
|
||||||
theme,
|
|
||||||
setTheme,
|
|
||||||
settings: themeSettings,
|
|
||||||
isDark: !themeSettings.light,
|
|
||||||
isLight: !!themeSettings.light,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// TODO: dedupe
|
|
||||||
function useEvent(name, onTrigger, useCapture = false) {
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener(name, onTrigger, useCapture);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener(name, onTrigger, useCapture);
|
|
||||||
};
|
|
||||||
}, [onTrigger]);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useTheme;
|
|
||||||
|
|
@ -11,8 +11,6 @@ module.exports = {
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
tertiary: '#82aaff',
|
|
||||||
highlight: '#bb8800',
|
|
||||||
// codemirror-theme settings
|
// codemirror-theme settings
|
||||||
background: 'var(--background)',
|
background: 'var(--background)',
|
||||||
lineBackground: 'var(--lineBackground)',
|
lineBackground: 'var(--lineBackground)',
|
||||||
|
|
@ -25,6 +23,9 @@ module.exports = {
|
||||||
gutterBorder: 'var(--gutterBorder)',
|
gutterBorder: 'var(--gutterBorder)',
|
||||||
lineHighlight: 'var(--lineHighlight)',
|
lineHighlight: 'var(--lineHighlight)',
|
||||||
},
|
},
|
||||||
|
spacing: {
|
||||||
|
'app-height': 'var(--app-height)',
|
||||||
|
},
|
||||||
typography(theme) {
|
typography(theme) {
|
||||||
return {
|
return {
|
||||||
DEFAULT: {
|
DEFAULT: {
|
||||||
|
|
@ -41,5 +42,5 @@ module.exports = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require('@tailwindcss/typography')],
|
plugins: [require('@tailwindcss/typography'), require('@tailwindcss/forms')],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue