react packaging

This commit is contained in:
Felix Roos 2022-05-17 21:40:17 +02:00
parent fb2f63ee26
commit b8a0559012
14 changed files with 792 additions and 96 deletions

31
package-lock.json generated
View File

@ -6740,6 +6740,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
@ -9374,6 +9375,7 @@
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@ -11100,11 +11102,10 @@
"packages/react": {
"name": "@strudel.cycles/react",
"version": "0.0.0",
"peer": true,
"dependencies": {
"@codemirror/lang-javascript": "^0.19.0",
"react": "^17.0.2",
"react-codemirror6": "^1.1.0",
"react-dom": "^17.0.2",
"react-hook-inview": "^4.5.0"
},
"devDependencies": {
@ -11113,8 +11114,14 @@
"@vitejs/plugin-react": "^1.3.0",
"autoprefixer": "^10.4.7",
"postcss": "^8.4.13",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"tailwindcss": "^3.0.24",
"vite": "^2.9.9"
},
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
},
"packages/react/node_modules/@types/react": {
@ -11141,6 +11148,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@ -11153,6 +11161,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
@ -13069,13 +13078,13 @@
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"@vitejs/plugin-react": "^1.3.0",
"autoprefixer": "10.4.7",
"postcss": "8.4.13",
"autoprefixer": "^10.4.7",
"postcss": "^8.4.13",
"react": "^17.0.2",
"react-codemirror6": "^1.1.0",
"react-dom": "^17.0.2",
"react-hook-inview": "^4.5.0",
"tailwindcss": "3.0.24",
"tailwindcss": "^3.0.24",
"vite": "^2.9.9"
},
"dependencies": {
@ -13103,6 +13112,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@ -13112,6 +13122,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
@ -16579,6 +16590,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
@ -17979,13 +17991,13 @@
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
"@vitejs/plugin-react": "^1.3.0",
"autoprefixer": "10.4.7",
"postcss": "8.4.13",
"autoprefixer": "^10.4.7",
"postcss": "^8.4.13",
"react": "^17.0.2",
"react-codemirror6": "^1.1.0",
"react-dom": "^17.0.2",
"react-hook-inview": "^4.5.0",
"tailwindcss": "3.0.24",
"tailwindcss": "^3.0.24",
"vite": "^2.9.9"
},
"dependencies": {
@ -18013,6 +18025,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
"integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
@ -18022,6 +18035,7 @@
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
"integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
@ -18610,6 +18624,7 @@
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
"integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"

View File

@ -8,10 +8,11 @@ pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
!dist
# Editor directories and files
.vscode/*
!.vscode/extensions.json

5
packages/react/dist/index.cjs.js vendored Normal file
View File

@ -0,0 +1,5 @@
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});var s=require("react"),re=require("react-codemirror6"),x=require("@codemirror/view"),G=require("@codemirror/state"),oe=require("@codemirror/lang-javascript"),i=require("@codemirror/highlight"),ae=require("react-hook-inview"),Q=require("@strudel.cycles/eval"),ne=require("@strudel.cycles/core/util.mjs"),f=require("@strudel.cycles/tone"),A=require("@strudel.cycles/core");function ce(e){return e&&typeof e=="object"&&"default"in e?e:{default:e}}function M(e){if(e&&e.__esModule)return e;var o=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});return e&&Object.keys(e).forEach(function(a){if(a!=="default"){var t=Object.getOwnPropertyDescriptor(e,a);Object.defineProperty(o,a,t.get?t:{enumerable:!0,get:function(){return e[a]}})}}),o.default=e,Object.freeze(o)}var m=ce(s);const se="#abb2bf",le="#7d8799",ie="#ffffff",ue="#21252b",j="rgba(0, 0, 0, 0.5)",de="transparent",B="#353a42",fe="rgba(128, 203, 196, 0.2)",z="#ffcc00",ge=x.EditorView.theme({"&":{color:"#ffffff",backgroundColor:de,fontSize:"15px","z-index":11},".cm-content":{caretColor:z,lineHeight:"22px"},".cm-line":{background:"#2C323699"},"&.cm-focused .cm-cursor":{borderLeftColor:z},"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":{backgroundColor:fe},".cm-panels":{backgroundColor:ue,color:"#ffffff"},".cm-panels.cm-panels-top":{borderBottom:"2px solid black"},".cm-panels.cm-panels-bottom":{borderTop:"2px solid black"},".cm-searchMatch":{backgroundColor:"#72a1ff59",outline:"1px solid #457dff"},".cm-searchMatch.cm-searchMatch-selected":{backgroundColor:"#6199ff2f"},".cm-activeLine":{backgroundColor:j},".cm-selectionMatch":{backgroundColor:"#aafe661a"},"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket":{backgroundColor:"#bad0f847",outline:"1px solid #515a6b"},".cm-gutters":{background:"#2C323699",color:"#676e95",border:"none"},".cm-activeLineGutter":{backgroundColor:j},".cm-foldPlaceholder":{backgroundColor:"transparent",border:"none",color:"#ddd"},".cm-tooltip":{border:"none",backgroundColor:B},".cm-tooltip .cm-tooltip-arrow:before":{borderTopColor:"transparent",borderBottomColor:"transparent"},".cm-tooltip .cm-tooltip-arrow:after":{borderTopColor:B,borderBottomColor:B},".cm-tooltip-autocomplete":{"& > ul > li[aria-selected]":{backgroundColor:j,color:se}}},{dark:!0}),me=i.HighlightStyle.define([{tag:i.tags.keyword,color:"#c792ea"},{tag:i.tags.operator,color:"#89ddff"},{tag:i.tags.special(i.tags.variableName),color:"#eeffff"},{tag:i.tags.typeName,color:"#f07178"},{tag:i.tags.atom,color:"#f78c6c"},{tag:i.tags.number,color:"#ff5370"},{tag:i.tags.definition(i.tags.variableName),color:"#82aaff"},{tag:i.tags.string,color:"#c3e88d"},{tag:i.tags.special(i.tags.string),color:"#f07178"},{tag:i.tags.comment,color:le},{tag:i.tags.variableName,color:"#f07178"},{tag:i.tags.tagName,color:"#ff5370"},{tag:i.tags.bracket,color:"#a2a1a4"},{tag:i.tags.meta,color:"#ffcb6b"},{tag:i.tags.attributeName,color:"#c792ea"},{tag:i.tags.propertyName,color:"#c792ea"},{tag:i.tags.className,color:"#decb6b"},{tag:i.tags.invalid,color:ie}]),he=[ge,me],D=G.StateEffect.define(),be=G.StateField.define({create(){return x.Decoration.none},update(e,o){try{for(let a of o.effects)a.is(D)&&(e=x.Decoration.set(a.value.flatMap(t=>(t.context.locations||[]).map(({start:r,end:c})=>{const u=t.context.color||"#FFCA28";let g=o.newDoc.line(r.line).from+r.column,l=o.newDoc.line(c.line).from+c.column;const h=o.newDoc.length;return g>h||l>h?void 0:x.Decoration.mark({attributes:{style:`outline: 1px solid ${u}`}}).range(g,l)})).filter(Boolean),!0));return e}catch{return e}},provide:e=>x.EditorView.decorations.from(e)});function U({value:e,onChange:o,onViewChanged:a,onCursor:t,options:r,editorDidMount:c}){return m.default.createElement(m.default.Fragment,null,m.default.createElement(re.CodeMirror,{onViewChange:a,style:{display:"flex",flexDirection:"column",flex:"1 0 auto"},value:e,onChange:o,extensions:[oe.javascript(),he,be]}))}let F;const pe=(e,o)=>{const a=e.getDoc().getValue(),t=W(a,o);F?.clear(),F=e.getDoc().markText(...t,{css:"background-color: #00007720"})};function J(e,o){const a=o.split(`
`);let t=0,r=0;for(let c=0;c<e;c++)r===a[t].length?(t++,r=0):r++;return{line:t,ch:r}}function K(e,o){const a=o.split(`
`);if(e.line>a.length)return 0;let t=0;for(let r=0;r<e.line;r++)t+=a[r].length+1;return t+=e.ch,t}function W(e,o){const a=K(o,e);let t,r,c,u;for(r=a,t=0;r>0&&(e[r-1]==="("?t--:e[r-1]===")"&&t++,t!==-1);)r--;for(c=r,r=a,t=0;r<e.length&&(e[r]==="("?t--:e[r]===")"&&t++,t!==1);)r++;return u=r,[c,u].map(g=>J(g,e))}var ve=Object.freeze(Object.defineProperty({__proto__:null,setHighlights:D,default:U,markParens:pe,offsetToPosition:J,positionToOffset:K,getCurrentParenArea:W},Symbol.toStringTag,{value:"Module"}));function ye(e){const{onEvent:o,onQuery:a,onSchedule:t,ready:r=!0,onDraw:c}=e,[u,g]=s.useState(!1),l=1,h=()=>Math.floor(f.Tone.getTransport().seconds/l),y=(v=h())=>{const q=new A.TimeSpan(v,v+1),R=a?.(new A.State(q))||[];t?.(R,v);const O=q.begin.valueOf();f.Tone.getTransport().cancel(O);const P=(v+1)*l-.5,N=Math.max(f.Tone.getTransport().seconds,P)+.1;f.Tone.getTransport().schedule(()=>{y(v+1)},N),R?.filter(p=>p.part.begin.equals(p.whole?.begin)).forEach(p=>{f.Tone.getTransport().schedule(w=>{o(w,p,f.Tone.getContext().currentTime),f.Tone.Draw.schedule(()=>{c?.(w,p)},w)},p.part.begin.valueOf())})};s.useEffect(()=>{r&&y()},[o,t,a,c,r]);const E=async()=>{g(!0),await f.Tone.start(),f.Tone.getTransport().start("+0.1")},b=()=>{f.Tone.getTransport().pause(),g(!1)};return{start:E,stop:b,onEvent:o,started:u,setStarted:g,toggle:()=>u?b():E(),query:y,activeCycle:h}}function Ce(e){return s.useEffect(()=>(window.addEventListener("message",e),()=>window.removeEventListener("message",e)),[e]),s.useCallback(o=>window.postMessage(o,"*"),[])}let we=()=>Math.floor((1+Math.random())*65536).toString(16).substring(1);const ke=e=>encodeURIComponent(btoa(e));function Te({tune:e,defaultSynth:o,autolink:a=!0,onEvent:t,onDraw:r}){const c=s.useMemo(()=>we(),[]),[u,g]=s.useState(e),[l,h]=s.useState(),[y,E]=s.useState(""),[b,C]=s.useState(),[v,q]=s.useState(!1),[R,O]=s.useState(""),[P,N]=s.useState(),p=s.useMemo(()=>u!==l||b,[u,l,b]),w=s.useCallback(d=>E(n=>n+`${n?`
`:""}${d}`),[]),X=s.useMemo(()=>{if(l&&!l.includes("strudel disable-highlighting"))return(d,n)=>r?.(d,n,l)},[l,r]),T=ye({onDraw:X,onEvent:s.useCallback((d,n,Z)=>{try{t?.(n),n.context.logs?.length&&n.context.logs.forEach(w);const{onTrigger:_,velocity:ee}=n.context;if(_)_(d,n,Z,1,n.wholeOrPart().begin.valueOf(),n.duration.valueOf());else if(o){const te=ne.getPlayableNoteValue(n);o.triggerAttackRelease(te,n.duration.valueOf(),d,ee)}else throw new Error("no defaultSynth passed to useRepl.")}catch(_){console.warn(_),_.message="unplayable event: "+_?.message,w(_.message)}},[t,w,o]),onQuery:s.useCallback(d=>{try{return P?.query(d)||[]}catch(n){return console.warn(n),n.message="query error: "+n.message,C(n),[]}},[P]),onSchedule:s.useCallback((d,n)=>Y(d,n),[]),ready:!!P&&!!l}),H=Ce(({data:{from:d,type:n}})=>{n==="start"&&d!==c&&(T.setStarted(!1),h(void 0))}),V=s.useCallback(async(d=u)=>{if(l&&!p){C(void 0),T.start();return}try{q(!0);const n=await Q.evaluate(d);T.start(),H({type:"start",from:c}),N(()=>n.pattern),a&&(window.location.hash="#"+encodeURIComponent(btoa(u))),O(ke(u)),C(void 0),h(d),q(!1)}catch(n){n.message="evaluation error: "+n.message,console.warn(n),C(n)}},[l,p,u,T,a,c,H]),Y=(d,n)=>{d.length};return{pending:v,code:u,setCode:g,pattern:P,error:b,cycle:T,setPattern:N,dirty:p,log:y,togglePlay:()=>{T.started?T.stop():V()},setActiveCode:h,activateCode:V,activeCode:l,pushLog:w,hash:R}}function L(...e){return e.filter(Boolean).join(" ")}let S=[],I;function _e({view:e,pattern:o,active:a}){s.useEffect(()=>{if(e)if(o&&a){let r=function(){try{const c=f.Tone.getTransport().seconds,u=[I||c,c+1/60];I=c+1/60,S=S.filter(l=>l.whole.end>c);const g=o.queryArc(...u).filter(l=>l.hasOnset());S=S.concat(g),e.dispatch({effects:D.of(S)})}catch{e.dispatch({effects:D.of([])})}t=requestAnimationFrame(r)},t=requestAnimationFrame(r);return()=>{cancelAnimationFrame(t)}}else S=[],e.dispatch({effects:D.of([])})},[o,a,e])}const Me="_container_10e1g_1",Ee="_header_10e1g_5",Pe="_buttons_10e1g_9",Se="_button_10e1g_9",qe="_buttonDisabled_10e1g_17",xe="_error_10e1g_21",De="_body_10e1g_25";var k={container:Me,header:Ee,buttons:Pe,button:Se,buttonDisabled:qe,error:xe,body:De};function $({type:e}){return React.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",className:"sc-h-5 sc-w-5",viewBox:"0 0 20 20",fill:"currentColor"},{refresh:React.createElement("path",{fillRule:"evenodd",d:"M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",clipRule:"evenodd"}),play:React.createElement("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",clipRule:"evenodd"}),pause:React.createElement("path",{fillRule:"evenodd",d:"M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",clipRule:"evenodd"})}[e])}Q.evalScope(f.Tone,Promise.resolve().then(function(){return M(require("@strudel.cycles/core"))}),Promise.resolve().then(function(){return M(require("@strudel.cycles/tone"))}),Promise.resolve().then(function(){return M(require("@strudel.cycles/tonal"))}),Promise.resolve().then(function(){return M(require("@strudel.cycles/mini"))}),Promise.resolve().then(function(){return M(require("@strudel.cycles/midi"))}),Promise.resolve().then(function(){return M(require("@strudel.cycles/xen"))}),Promise.resolve().then(function(){return M(require("@strudel.cycles/webaudio"))}));const Re=new f.Tone.PolySynth().chain(new f.Tone.Gain(.5),f.Tone.Destination).set({oscillator:{type:"triangle"},envelope:{release:.01}});function Ne({tune:e}){const{code:o,setCode:a,pattern:t,activateCode:r,error:c,cycle:u,dirty:g,togglePlay:l}=Te({tune:e,defaultSynth:Re,autolink:!1}),[h,y]=s.useState(),[E,b]=ae.useInView({threshold:.01}),C=s.useRef(),v=s.useMemo(()=>(b&&(C.current=!0),b||C.current),[b]);return _e({view:h,pattern:t,active:u.started}),m.default.createElement("div",{className:k.container,ref:E},m.default.createElement("div",{className:k.header},m.default.createElement("div",{className:k.buttons},m.default.createElement("button",{className:L(k.button,u.started?"sc-animate-pulse":""),onClick:()=>l()},m.default.createElement($,{type:u.started?"pause":"play"})),m.default.createElement("button",{className:L(g?k.button:k.buttonDisabled),onClick:()=>r()},m.default.createElement($,{type:"refresh"}))),c&&m.default.createElement("div",{className:k.error},c.message)),m.default.createElement("div",{className:k.body},v&&m.default.createElement(U,{value:o,onChange:a,onViewChanged:y})))}exports.CodeMirror=ve;exports.MiniRepl=Ne;

663
packages/react/dist/index.es.js vendored Normal file
View File

@ -0,0 +1,663 @@
import React$1, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { CodeMirror as CodeMirror$1 } from 'react-codemirror6';
import { EditorView, Decoration } from '@codemirror/view';
import { StateEffect, StateField } from '@codemirror/state';
import { javascript } from '@codemirror/lang-javascript';
import { HighlightStyle, tags } from '@codemirror/highlight';
import { useInView } from 'react-hook-inview';
import { evaluate, evalScope } from '@strudel.cycles/eval';
import { getPlayableNoteValue } from '@strudel.cycles/core/util.mjs';
import { Tone } from '@strudel.cycles/tone';
import { TimeSpan, State } from '@strudel.cycles/core';
/*
Credits for color palette:
Author: Mattia Astorino (http://github.com/equinusocio)
Website: https://material-theme.site/
*/
const ivory = '#abb2bf',
stone = '#7d8799', // Brightened compared to original to increase contrast
invalid = '#ffffff',
darkBackground = '#21252b',
highlightBackground = 'rgba(0, 0, 0, 0.5)',
// background = '#292d3e',
background = 'transparent',
tooltipBackground = '#353a42',
selection = 'rgba(128, 203, 196, 0.2)',
cursor = '#ffcc00';
/// The editor theme styles for Material Palenight.
const materialPalenightTheme = EditorView.theme(
{
// done
'&': {
color: '#ffffff',
backgroundColor: background,
fontSize: '15px',
'z-index': 11,
},
// done
'.cm-content': {
caretColor: cursor,
lineHeight: '22px',
},
'.cm-line': {
background: '#2C323699',
},
// done
'&.cm-focused .cm-cursor': {
borderLeftColor: cursor,
},
'&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': {
backgroundColor: selection,
},
'.cm-panels': { backgroundColor: darkBackground, color: '#ffffff' },
'.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
'.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
// done, use onedarktheme
'.cm-searchMatch': {
backgroundColor: '#72a1ff59',
outline: '1px solid #457dff',
},
'.cm-searchMatch.cm-searchMatch-selected': {
backgroundColor: '#6199ff2f',
},
'.cm-activeLine': { backgroundColor: highlightBackground },
'.cm-selectionMatch': { backgroundColor: '#aafe661a' },
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
backgroundColor: '#bad0f847',
outline: '1px solid #515a6b',
},
// done
'.cm-gutters': {
background: '#2C323699',
color: '#676e95',
border: 'none',
},
'.cm-activeLineGutter': {
backgroundColor: highlightBackground,
},
'.cm-foldPlaceholder': {
backgroundColor: 'transparent',
border: 'none',
color: '#ddd',
},
'.cm-tooltip': {
border: 'none',
backgroundColor: tooltipBackground,
},
'.cm-tooltip .cm-tooltip-arrow:before': {
borderTopColor: 'transparent',
borderBottomColor: 'transparent',
},
'.cm-tooltip .cm-tooltip-arrow:after': {
borderTopColor: tooltipBackground,
borderBottomColor: tooltipBackground,
},
'.cm-tooltip-autocomplete': {
'& > ul > li[aria-selected]': {
backgroundColor: highlightBackground,
color: ivory,
},
},
},
{ dark: true },
);
/// The highlighting style for code in the Material Palenight theme.
const materialPalenightHighlightStyle = HighlightStyle.define([
{ tag: tags.keyword, color: '#c792ea' },
{ tag: tags.operator, color: '#89ddff' },
{ tag: tags.special(tags.variableName), color: '#eeffff' },
{ tag: tags.typeName, color: '#f07178' },
{ tag: tags.atom, color: '#f78c6c' },
{ tag: tags.number, color: '#ff5370' },
{ tag: tags.definition(tags.variableName), color: '#82aaff' },
{ tag: tags.string, color: '#c3e88d' },
{ tag: tags.special(tags.string), color: '#f07178' },
{ tag: tags.comment, color: stone },
{ tag: tags.variableName, color: '#f07178' },
{ tag: tags.tagName, color: '#ff5370' },
{ tag: tags.bracket, color: '#a2a1a4' },
{ tag: tags.meta, color: '#ffcb6b' },
{ tag: tags.attributeName, color: '#c792ea' },
{ tag: tags.propertyName, color: '#c792ea' },
{ tag: tags.className, color: '#decb6b' },
{ tag: tags.invalid, color: invalid },
]);
/// Extension to enable the Material Palenight theme (both the editor theme and
/// the highlight style).
// : Extension
const materialPalenight = [materialPalenightTheme, materialPalenightHighlightStyle];
const setHighlights = StateEffect.define();
const highlightField = StateField.define({
create() {
return Decoration.none;
},
update(highlights, tr) {
try {
for (let e of tr.effects) {
if (e.is(setHighlights)) {
highlights = Decoration.set(e.value.flatMap((hap) => (hap.context.locations || []).map(({ start, end }) => {
const color = hap.context.color || "#FFCA28";
let from = tr.newDoc.line(start.line).from + start.column;
let to = tr.newDoc.line(end.line).from + end.column;
const l = tr.newDoc.length;
if (from > l || to > l) {
return;
}
const mark = Decoration.mark({ attributes: { style: `outline: 1px solid ${color}` } });
return mark.range(from, to);
})).filter(Boolean), true);
}
}
return highlights;
} catch (err) {
return highlights;
}
},
provide: (f) => EditorView.decorations.from(f)
});
function CodeMirror({ value, onChange, onViewChanged, onCursor, options, editorDidMount }) {
return /* @__PURE__ */ React$1.createElement(React$1.Fragment, null, /* @__PURE__ */ React$1.createElement(CodeMirror$1, {
onViewChange: onViewChanged,
style: {
display: "flex",
flexDirection: "column",
flex: "1 0 auto"
},
value,
onChange,
extensions: [
javascript(),
materialPalenight,
highlightField
]
}));
}
let parenMark;
const markParens = (editor, data) => {
const v = editor.getDoc().getValue();
const marked = getCurrentParenArea(v, data);
parenMark?.clear();
parenMark = editor.getDoc().markText(...marked, { css: "background-color: #00007720" });
};
function offsetToPosition(offset, code) {
const lines = code.split("\n");
let line = 0;
let ch = 0;
for (let i = 0; i < offset; i++) {
if (ch === lines[line].length) {
line++;
ch = 0;
} else {
ch++;
}
}
return { line, ch };
}
function positionToOffset(position, code) {
const lines = code.split("\n");
if (position.line > lines.length) {
return 0;
}
let offset = 0;
for (let i = 0; i < position.line; i++) {
offset += lines[i].length + 1;
}
offset += position.ch;
return offset;
}
function getCurrentParenArea(code, caretPosition) {
const caret = positionToOffset(caretPosition, code);
let open, i, begin, end;
i = caret;
open = 0;
while (i > 0) {
if (code[i - 1] === "(") {
open--;
} else if (code[i - 1] === ")") {
open++;
}
if (open === -1) {
break;
}
i--;
}
begin = i;
i = caret;
open = 0;
while (i < code.length) {
if (code[i] === "(") {
open--;
} else if (code[i] === ")") {
open++;
}
if (open === 1) {
break;
}
i++;
}
end = i;
return [begin, end].map((o) => offsetToPosition(o, code));
}
var CodeMirror6 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
setHighlights: setHighlights,
'default': CodeMirror,
markParens: markParens,
offsetToPosition: offsetToPosition,
positionToOffset: positionToOffset,
getCurrentParenArea: getCurrentParenArea
}, Symbol.toStringTag, { value: 'Module' }));
/*
useCycle.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/useCycle.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* export declare interface UseCycleProps {
onEvent: ToneEventCallback<any>;
onQuery?: (state: State) => Hap[];
onSchedule?: (events: Hap[], cycle: number) => void;
onDraw?: ToneEventCallback<any>;
ready?: boolean; // if false, query will not be called on change props
} */
// function useCycle(props: UseCycleProps) {
function useCycle(props) {
// onX must use useCallback!
const { onEvent, onQuery, onSchedule, ready = true, onDraw } = props;
const [started, setStarted] = useState(false);
const cycleDuration = 1;
const activeCycle = () => Math.floor(Tone.getTransport().seconds / cycleDuration);
// pull events with onQuery + count up to next cycle
const query = (cycle = activeCycle()) => {
const timespan = new TimeSpan(cycle, cycle + 1);
const events = onQuery?.(new State(timespan)) || [];
onSchedule?.(events, cycle);
// cancel events after current query. makes sure no old events are player for rescheduled cycles
// console.log('schedule', cycle);
// query next cycle in the middle of the current
const cancelFrom = timespan.begin.valueOf();
Tone.getTransport().cancel(cancelFrom);
// const queryNextTime = (cycle + 1) * cycleDuration - 0.1;
const queryNextTime = (cycle + 1) * cycleDuration - 0.5;
// if queryNextTime would be before current time, execute directly (+0.1 for safety that it won't miss)
const t = Math.max(Tone.getTransport().seconds, queryNextTime) + 0.1;
Tone.getTransport().schedule(() => {
query(cycle + 1);
}, t);
// schedule events for next cycle
events
?.filter((event) => event.part.begin.equals(event.whole?.begin))
.forEach((event) => {
Tone.getTransport().schedule((time) => {
onEvent(time, event, Tone.getContext().currentTime);
Tone.Draw.schedule(() => {
// do drawing or DOM manipulation here
onDraw?.(time, event);
}, time);
}, event.part.begin.valueOf());
});
};
useEffect(() => {
ready && query();
}, [onEvent, onSchedule, onQuery, onDraw, ready]);
const start = async () => {
setStarted(true);
await Tone.start();
Tone.getTransport().start('+0.1');
};
const stop = () => {
Tone.getTransport().pause();
setStarted(false);
};
const toggle = () => (started ? stop() : start());
return {
start,
stop,
onEvent,
started,
setStarted,
toggle,
query,
activeCycle,
};
}
/*
usePostMessage.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/usePostMessage.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
function usePostMessage(listener) {
useEffect(() => {
window.addEventListener('message', listener);
return () => window.removeEventListener('message', listener);
}, [listener]);
return useCallback((data) => window.postMessage(data, '*'), []);
}
/*
useRepl.mjs - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/useRepl.mjs>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
let s4 = () => {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
};
const generateHash = (code) => encodeURIComponent(btoa(code));
function useRepl({ tune, defaultSynth, autolink = true, onEvent, onDraw: onDrawProp }) {
const id = useMemo(() => s4(), []);
const [code, setCode] = useState(tune);
const [activeCode, setActiveCode] = useState();
const [log, setLog] = useState('');
const [error, setError] = useState();
const [pending, setPending] = useState(false);
const [hash, setHash] = useState('');
const [pattern, setPattern] = useState();
const dirty = useMemo(() => code !== activeCode || error, [code, activeCode, error]);
const pushLog = useCallback((message) => setLog((log) => log + `${log ? '\n\n' : ''}${message}`), []);
// below block allows disabling the highlighting by including "strudel disable-highlighting" in the code (as comment)
const onDraw = useMemo(() => {
if (activeCode && !activeCode.includes('strudel disable-highlighting')) {
return (time, event) => onDrawProp?.(time, event, activeCode);
}
}, [activeCode, onDrawProp]);
// cycle hook to control scheduling
const cycle = useCycle({
onDraw,
onEvent: useCallback(
(time, event, currentTime) => {
try {
onEvent?.(event);
if (event.context.logs?.length) {
event.context.logs.forEach(pushLog);
}
const { onTrigger, velocity } = event.context;
if (!onTrigger) {
if (defaultSynth) {
const note = getPlayableNoteValue(event);
defaultSynth.triggerAttackRelease(note, event.duration.valueOf(), time, velocity);
} else {
throw new Error('no defaultSynth passed to useRepl.');
}
/* console.warn('no instrument chosen', event);
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */
} else {
onTrigger(
time,
event,
currentTime,
1 /* cps */,
event.wholeOrPart().begin.valueOf(),
event.duration.valueOf(),
);
}
} catch (err) {
console.warn(err);
err.message = 'unplayable event: ' + err?.message;
pushLog(err.message); // not with setError, because then we would have to setError(undefined) on next playable event
}
},
[onEvent, pushLog, defaultSynth],
),
onQuery: useCallback(
(state) => {
try {
return pattern?.query(state) || [];
} catch (err) {
console.warn(err);
err.message = 'query error: ' + err.message;
setError(err);
return [];
}
},
[pattern],
),
onSchedule: useCallback((_events, cycle) => logCycle(_events, cycle), []),
ready: !!pattern && !!activeCode,
});
const broadcast = usePostMessage(({ data: { from, type } }) => {
if (type === 'start' && from !== id) {
// console.log('message', from, type);
cycle.setStarted(false);
setActiveCode(undefined);
}
});
const activateCode = useCallback(
async (_code = code) => {
if (activeCode && !dirty) {
setError(undefined);
cycle.start();
return;
}
try {
setPending(true);
const parsed = await evaluate(_code);
cycle.start();
broadcast({ type: 'start', from: id });
setPattern(() => parsed.pattern);
if (autolink) {
window.location.hash = '#' + encodeURIComponent(btoa(code));
}
setHash(generateHash(code));
setError(undefined);
setActiveCode(_code);
setPending(false);
} catch (err) {
err.message = 'evaluation error: ' + err.message;
console.warn(err);
setError(err);
}
},
[activeCode, dirty, code, cycle, autolink, id, broadcast],
);
// logs events of cycle
const logCycle = (_events, cycle) => {
if (_events.length) ;
};
const togglePlay = () => {
if (!cycle.started) {
activateCode();
} else {
cycle.stop();
}
};
return {
pending,
code,
setCode,
pattern,
error,
cycle,
setPattern,
dirty,
log,
togglePlay,
setActiveCode,
activateCode,
activeCode,
pushLog,
hash,
};
}
/*
cx.js - <short description TODO>
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/cx.js>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
function cx(...classes) {
// : Array<string | undefined>
return classes.filter(Boolean).join(' ');
}
let highlights = []; // actively highlighted events
let lastEnd;
function useHighlighting({ view, pattern, active }) {
useEffect(() => {
if (view) {
if (pattern && active) {
let frame = requestAnimationFrame(updateHighlights);
function updateHighlights() {
try {
const audioTime = Tone.getTransport().seconds;
const span = [lastEnd || audioTime, audioTime + 1 / 60];
lastEnd = audioTime + 1 / 60;
highlights = highlights.filter((hap) => hap.whole.end > audioTime); // keep only highlights that are still active
const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset());
highlights = highlights.concat(haps); // add potential new onsets
view.dispatch({ effects: setHighlights.of(highlights) }); // highlight all still active + new active haps
} catch (err) {
// console.log('error in updateHighlights', err);
view.dispatch({ effects: setHighlights.of([]) });
}
frame = requestAnimationFrame(updateHighlights);
}
return () => {
cancelAnimationFrame(frame);
};
} else {
highlights = [];
view.dispatch({ effects: setHighlights.of([]) });
}
}
}, [pattern, active, view]);
}
var tailwind = '';
const container = "_container_10e1g_1";
const header = "_header_10e1g_5";
const buttons = "_buttons_10e1g_9";
const button = "_button_10e1g_9";
const buttonDisabled = "_buttonDisabled_10e1g_17";
const error = "_error_10e1g_21";
const body = "_body_10e1g_25";
var styles = {
container: container,
header: header,
buttons: buttons,
button: button,
buttonDisabled: buttonDisabled,
error: error,
body: body
};
function Icon({ type }) {
return /* @__PURE__ */ React.createElement("svg", {
xmlns: "http://www.w3.org/2000/svg",
className: "sc-h-5 sc-w-5",
viewBox: "0 0 20 20",
fill: "currentColor"
}, {
refresh: /* @__PURE__ */ React.createElement("path", {
fillRule: "evenodd",
d: "M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z",
clipRule: "evenodd"
}),
play: /* @__PURE__ */ React.createElement("path", {
fillRule: "evenodd",
d: "M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z",
clipRule: "evenodd"
}),
pause: /* @__PURE__ */ React.createElement("path", {
fillRule: "evenodd",
d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z",
clipRule: "evenodd"
})
}[type]);
}
evalScope(Tone, import('@strudel.cycles/core'), import('@strudel.cycles/tone'), import('@strudel.cycles/tonal'), import('@strudel.cycles/mini'), import('@strudel.cycles/midi'), import('@strudel.cycles/xen'), import('@strudel.cycles/webaudio'));
const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination).set({
oscillator: { type: "triangle" },
envelope: {
release: 0.01
}
});
function MiniRepl({ tune }) {
const { code, setCode, pattern, activateCode, error, cycle, dirty, togglePlay } = useRepl({
tune,
defaultSynth,
autolink: false
});
const [view, setView] = useState();
const [ref, isVisible] = useInView({
threshold: 0.01
});
const wasVisible = useRef();
const show = useMemo(() => {
if (isVisible) {
wasVisible.current = true;
}
return isVisible || wasVisible.current;
}, [isVisible]);
useHighlighting({ view, pattern, active: cycle.started });
return /* @__PURE__ */ React$1.createElement("div", {
className: styles.container,
ref
}, /* @__PURE__ */ React$1.createElement("div", {
className: styles.header
}, /* @__PURE__ */ React$1.createElement("div", {
className: styles.buttons
}, /* @__PURE__ */ React$1.createElement("button", {
className: cx(styles.button, cycle.started ? "sc-animate-pulse" : ""),
onClick: () => togglePlay()
}, /* @__PURE__ */ React$1.createElement(Icon, {
type: cycle.started ? "pause" : "play"
})), /* @__PURE__ */ React$1.createElement("button", {
className: cx(dirty ? styles.button : styles.buttonDisabled),
onClick: () => activateCode()
}, /* @__PURE__ */ React$1.createElement(Icon, {
type: "refresh"
}))), error && /* @__PURE__ */ React$1.createElement("div", {
className: styles.error
}, error.message)), /* @__PURE__ */ React$1.createElement("div", {
className: styles.body
}, show && /* @__PURE__ */ React$1.createElement(CodeMirror, {
value: code,
onChange: setCode,
onViewChanged: setView
})));
}
export { CodeMirror6 as CodeMirror, MiniRepl };

1
packages/react/dist/style.css vendored Normal file
View File

@ -0,0 +1 @@
*,:before,:after{--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.sc-h-5{height:1.25rem}.sc-w-5{width:1.25rem}@keyframes sc-pulse{50%{opacity:.5}}.sc-animate-pulse{animation:sc-pulse 2s cubic-bezier(.4,0,.6,1) infinite}._container_10e1g_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(68 76 87 / var(--tw-bg-opacity))}._header_10e1g_5{display:flex;justify-content:space-between;border-top-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}._buttons_10e1g_9{display:flex}._button_10e1g_9{display:flex;width:4rem;cursor:pointer;align-items:center;justify-content:center;border-right-width:1px;--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity));--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}._button_10e1g_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_10e1g_17{display:flex;width:4rem;cursor:pointer;cursor:not-allowed;align-items:center;justify-content:center;--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity));padding:.25rem;--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}._error_10e1g_21{padding:.25rem;text-align:right;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity))}._body_10e1g_25{position:relative;overflow:auto}

View File

@ -2,6 +2,18 @@
"name": "@strudel.cycles/react",
"private": true,
"version": "0.0.0",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
"exports": {
".": {
"require": "./dist/index.cjs.js",
"import": "./dist/index.es.js"
},
"./dist/style.css": "./dist/style.css"
},
"files": [
"dist"
],
"scripts": {
"dev": "vite",
"build": "vite build",

View File

@ -1,5 +1,5 @@
import React from 'react';
import MiniRepl from './components/MiniRepl';
import { MiniRepl } from './components/MiniRepl';
import 'tailwindcss/tailwind.css';
function App() {

View File

@ -0,0 +1,31 @@
export function Icon({ type }) {
return (
<svg xmlns="http://www.w3.org/2000/svg" className="sc-h-5 sc-w-5" viewBox="0 0 20 20" fill="currentColor">
{
{
refresh: (
<path
fillRule="evenodd"
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
clipRule="evenodd"
/>
),
play: (
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"
clipRule="evenodd"
/>
),
pause: (
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z"
clipRule="evenodd"
/>
),
}[type]
}
</svg>
);
}

View File

@ -1,14 +1,16 @@
import React, { useState } from 'react';
import React, { useState, useMemo, useRef } from 'react';
import { useInView } from 'react-hook-inview';
import { Tone } from '@strudel.cycles/tone';
import { evalScope } from '@strudel.cycles/eval';
import useRepl from '../hooks/useRepl.mjs';
import cx from '../cx';
import useHighlighting from '../hooks/useHighlighting.mjs';
import CodeMirror6 from './CodeMirror6';
import 'tailwindcss/tailwind.css';
import styles from './MiniRepl.module.css';
import { Icon } from './Icon';
import { Tone } from '@strudel.cycles/tone';
import { evalScope } from '@strudel.cycles/eval';
evalScope(
Tone,
import('@strudel.cycles/core'),
@ -27,85 +29,40 @@ const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destina
},
});
// "balanced" | "interactive" | "playback";
// Tone.setContext(new Tone.Context({ latencyHint: 'playback', lookAhead: 1 }));
function MiniRepl({ tune, maxHeight = 500 }) {
export function MiniRepl({ tune }) {
const { code, setCode, pattern, activateCode, error, cycle, dirty, togglePlay } = useRepl({
tune,
defaultSynth,
autolink: false,
});
const lines = code.split('\n').length;
const [view, setView] = useState();
const [ref, isVisible] = useInView({
threshold: 0.01,
});
const wasVisible = useRef();
const show = useMemo(() => {
if (isVisible) {
wasVisible.current = true;
}
return isVisible || wasVisible.current;
}, [isVisible]);
useHighlighting({ view, pattern, active: cycle.started });
return (
<div className="sc-rounded-md sc-overflow-hidden sc-bg-[#444C57]" ref={ref}>
<div className="sc-flex sc-justify-between sc-bg-slate-700 sc-border-t sc-border-slate-500">
<div className="sc-flex">
<button
className={cx(
'sc-w-16 sc-flex sc-items-center sc-justify-center sc-p-1 sc-bg-slate-700 sc-border-r sc-border-slate-500 sc-text-white sc-hover:bg-slate-600',
cycle.started ? 'sc-animate-pulse' : '',
)}
onClick={() => togglePlay()}
>
{!cycle.started ? (
<svg xmlns="http://www.w3.org/2000/svg" className="sc-h-5 sc-w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"
clipRule="evenodd"
/>
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" className="sc-h-5 sc-w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
)}
<div className={styles.container} ref={ref}>
<div className={styles.header}>
<div className={styles.buttons}>
<button className={cx(styles.button, cycle.started ? 'sc-animate-pulse' : '')} onClick={() => togglePlay()}>
<Icon type={cycle.started ? 'pause' : 'play'} />
</button>
<button
className={cx(
'sc-w-16 sc-flex sc-items-center sc-justify-center sc-p-1 sc-border-slate-500 sc-hover:bg-slate-600',
dirty
? 'sc-bg-slate-700 sc-border-r sc-border-slate-500 sc-text-white'
: 'sc-bg-slate-600 sc-text-slate-400 sc-cursor-not-allowed',
)}
onClick={() => activateCode()}
>
<svg xmlns="http://www.w3.org/2000/svg" className="sc-h-5 sc-w-5" viewBox="0 0 20 20" fill="currentColor">
<path
fillRule="evenodd"
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
clipRule="evenodd"
/>
</svg>
<button className={cx(dirty ? styles.button : styles.buttonDisabled)} onClick={() => activateCode()}>
<Icon type="refresh" />
</button>
</div>
<div className="sc-text-right sc-p-1 sc-text-sm">
{error && <span className="sc-text-red-200">{error.message}</span>}
</div>{' '}
{error && <div className={styles.error}>{error.message}</div>}
</div>
<div className="sc-flex sc-space-y-0 sc-overflow-auto sc-relative">
{isVisible && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} />}
<div className={styles.body}>
{show && <CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} />}
</div>
{/* <div className="bg-slate-700 border-t border-slate-500 content-right pr-2 text-right">
<a href={`https://strudel.tidalcycles.org/#${hash}`} className="text-white items-center inline-flex">
<span>open in REPL</span>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z" />
<path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z" />
</svg>
</a>
</div> */}
</div>
);
}
export default MiniRepl;

View File

@ -0,0 +1,27 @@
.container {
@apply sc-rounded-md sc-overflow-hidden sc-bg-[#444C57];
}
.header {
@apply sc-flex sc-justify-between sc-bg-slate-700 sc-border-t sc-border-slate-500;
}
.buttons {
@apply sc-flex;
}
.button {
@apply sc-cursor-pointer sc-w-16 sc-flex sc-items-center sc-justify-center sc-p-1 sc-bg-slate-700 sc-border-r sc-border-slate-500 sc-text-white hover:sc-bg-slate-600;
}
.buttonDisabled {
@apply sc-cursor-pointer sc-w-16 sc-flex sc-items-center sc-justify-center sc-p-1 sc-bg-slate-600 sc-text-slate-400 sc-cursor-not-allowed;
}
.error {
@apply sc-text-right sc-p-1 sc-text-sm sc-text-red-200;
}
.body {
@apply sc-overflow-auto sc-relative;
}

View File

@ -1,4 +1,4 @@
// import 'tailwindcss/tailwind.css';
export * as CodeMirror from './components/CodeMirror6';
export * as MiniRepl from './components/MiniRepl';
export * from './components/MiniRepl';

View File

@ -30,6 +30,7 @@ export default defineConfig({
'@strudel.cycles/midi',
'@strudel.cycles/xen',
'@strudel.cycles/serial',
'@strudel.cycles/webaudio',
'@codemirror/view',
'@codemirror/highlight',
'@codemirror/state'

View File

@ -8,6 +8,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import Tutorial from './tutorial.mdx';
import './style.css';
import '@strudel.cycles/react/dist/style.css';
ReactDOM.render(
@ -33,21 +34,3 @@ ReactDOM.render(
</React.StrictMode>,
document.getElementById('root')
);
/*
// for pragmatic reasons, I just added the tailwind classes from MiniRepl here to make them work
// TODO: find a way to "export" tailwind classes from package
rounded-md overflow-hidden bg-[#444C57]
flex justify-between bg-slate-700 border-t border-slate-500
flex
w-16 flex items-center justify-center p-1 bg-slate-700 border-r border-slate-500 text-white hover:bg-slate-600
animate-pulse
h-5 w-5
w-16 flex items-center justify-center p-1 border-slate-500 hover:bg-slate-600
bg-slate-700 border-r border-slate-500 text-white
bg-slate-600 text-slate-400 cursor-not-allowed
text-right p-1 text-sm
text-red-200
flex space-y-0 overflow-auto relative
*/

View File

@ -1,4 +1,4 @@
import MiniRepl from '@strudel.cycles/react/src/components/MiniRepl';
import { MiniRepl } from '@strudel.cycles/react';
# What is Strudel?