mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-27 13:38:40 +00:00
big style update + new log system
This commit is contained in:
parent
3e6c3fda60
commit
45c7b29a96
@ -13,9 +13,10 @@ export class Cyclist {
|
|||||||
cps = 1; // TODO
|
cps = 1; // TODO
|
||||||
getTime;
|
getTime;
|
||||||
phase = 0;
|
phase = 0;
|
||||||
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1 }) {
|
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1, onLog }) {
|
||||||
this.getTime = getTime;
|
this.getTime = getTime;
|
||||||
this.onToggle = onToggle;
|
this.onToggle = onToggle;
|
||||||
|
this.onLog = onLog;
|
||||||
this.latency = latency;
|
this.latency = latency;
|
||||||
const round = (x) => Math.round(x * 1000) / 1000;
|
const round = (x) => Math.round(x * 1000) / 1000;
|
||||||
this.clock = createClock(
|
this.clock = createClock(
|
||||||
@ -56,14 +57,17 @@ export class Cyclist {
|
|||||||
if (!this.pattern) {
|
if (!this.pattern) {
|
||||||
throw new Error('Scheduler: no pattern set! call .setPattern first.');
|
throw new Error('Scheduler: no pattern set! call .setPattern first.');
|
||||||
}
|
}
|
||||||
|
this.onLog?.('start');
|
||||||
this.clock.start();
|
this.clock.start();
|
||||||
this.setStarted(true);
|
this.setStarted(true);
|
||||||
}
|
}
|
||||||
pause() {
|
pause() {
|
||||||
|
this.onLog?.('pause');
|
||||||
this.clock.pause();
|
this.clock.pause();
|
||||||
this.setStarted(false);
|
this.setStarted(false);
|
||||||
}
|
}
|
||||||
stop() {
|
stop() {
|
||||||
|
this.onLog?.('stop');
|
||||||
this.clock.stop();
|
this.clock.stop();
|
||||||
this.setStarted(false);
|
this.setStarted(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export function repl({
|
|||||||
getTime,
|
getTime,
|
||||||
transpiler,
|
transpiler,
|
||||||
onToggle,
|
onToggle,
|
||||||
|
onLog,
|
||||||
}) {
|
}) {
|
||||||
const scheduler = new Cyclist({
|
const scheduler = new Cyclist({
|
||||||
interval,
|
interval,
|
||||||
@ -25,6 +26,7 @@ export function repl({
|
|||||||
onError: onSchedulerError,
|
onError: onSchedulerError,
|
||||||
getTime,
|
getTime,
|
||||||
onToggle,
|
onToggle,
|
||||||
|
onLog: (message) => onLog?.(`[clock] ${message}`),
|
||||||
});
|
});
|
||||||
const evaluate = async (code, autostart = true) => {
|
const evaluate = async (code, autostart = true) => {
|
||||||
if (!code) {
|
if (!code) {
|
||||||
@ -33,11 +35,13 @@ export function repl({
|
|||||||
try {
|
try {
|
||||||
beforeEval({ code });
|
beforeEval({ code });
|
||||||
const { pattern } = await _evaluate(code, transpiler);
|
const { pattern } = await _evaluate(code, transpiler);
|
||||||
|
onLog?.(`[eval] code updated`);
|
||||||
scheduler.setPattern(pattern, autostart);
|
scheduler.setPattern(pattern, autostart);
|
||||||
afterEval({ code, pattern });
|
afterEval({ code, pattern });
|
||||||
return pattern;
|
return pattern;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`eval error: ${err.message}`);
|
// console.warn(`[repl] eval error: ${err.message}`);
|
||||||
|
onLog?.(`[eval] error: ${err.message}`, 'error');
|
||||||
onEvalError?.(err);
|
onEvalError?.(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
36
packages/react/dist/index.cjs.js
vendored
36
packages/react/dist/index.cjs.js
vendored
File diff suppressed because one or more lines are too long
1516
packages/react/dist/index.es.js
vendored
1516
packages/react/dist/index.es.js
vendored
File diff suppressed because one or more lines are too long
2
packages/react/dist/style.css
vendored
2
packages/react/dist/style.css
vendored
@ -1 +1 @@
|
|||||||
.cm-editor{background-color:transparent!important;height:100%;z-index:11;font-size:16px}.cm-theme-light{width:100%}.cm-line>*{background:#00000095}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--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: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--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_3i85k_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(34 34 34 / var(--tw-bg-opacity))}._header_3i85k_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_3i85k_9{display:flex}._button_3i85k_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_3i85k_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_3i85k_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_3i85k_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_3i85k_25{position:relative;overflow:auto}
|
.cm-editor{background-color:transparent!important;height:100%;z-index:11;font-size:18px}.cm-theme-light{width:100%}.cm-line>*{background:#00000095}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--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: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--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_3i85k_1{overflow:hidden;border-radius:.375rem;--tw-bg-opacity: 1;background-color:rgb(34 34 34 / var(--tw-bg-opacity))}._header_3i85k_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_3i85k_9{display:flex}._button_3i85k_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_3i85k_9:hover{--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}._buttonDisabled_3i85k_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_3i85k_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_3i85k_25{position:relative;overflow:auto}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
font-size: 16px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-theme-light {
|
.cm-theme-light {
|
||||||
|
|||||||
@ -2,7 +2,17 @@ import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
|
|||||||
import { repl } from '@strudel.cycles/core/repl.mjs';
|
import { repl } from '@strudel.cycles/core/repl.mjs';
|
||||||
import { transpiler } from '@strudel.cycles/transpiler';
|
import { transpiler } from '@strudel.cycles/transpiler';
|
||||||
|
|
||||||
function useStrudel({ defaultOutput, interval, getTime, evalOnMount = false, initialCode = '', autolink = false }) {
|
function useStrudel({
|
||||||
|
defaultOutput,
|
||||||
|
interval,
|
||||||
|
getTime,
|
||||||
|
evalOnMount = false,
|
||||||
|
initialCode = '',
|
||||||
|
autolink = false,
|
||||||
|
afterEval,
|
||||||
|
onEvalError,
|
||||||
|
onLog,
|
||||||
|
}) {
|
||||||
// scheduler
|
// scheduler
|
||||||
const [schedulerError, setSchedulerError] = useState();
|
const [schedulerError, setSchedulerError] = useState();
|
||||||
const [evalError, setEvalError] = useState();
|
const [evalError, setEvalError] = useState();
|
||||||
@ -17,15 +27,18 @@ function useStrudel({ defaultOutput, interval, getTime, evalOnMount = false, ini
|
|||||||
() =>
|
() =>
|
||||||
repl({
|
repl({
|
||||||
interval,
|
interval,
|
||||||
|
onLog,
|
||||||
defaultOutput,
|
defaultOutput,
|
||||||
onSchedulerError: setSchedulerError,
|
onSchedulerError: setSchedulerError,
|
||||||
onEvalError: setEvalError,
|
onEvalError: (err) => {
|
||||||
|
setEvalError(err);
|
||||||
|
onEvalError?.(err);
|
||||||
|
},
|
||||||
getTime,
|
getTime,
|
||||||
transpiler,
|
transpiler,
|
||||||
beforeEval: ({ code }) => {
|
beforeEval: ({ code }) => {
|
||||||
setCode(code);
|
setCode(code);
|
||||||
},
|
},
|
||||||
onEvalError: setEvalError,
|
|
||||||
afterEval: ({ pattern: _pattern, code }) => {
|
afterEval: ({ pattern: _pattern, code }) => {
|
||||||
setActiveCode(code);
|
setActiveCode(code);
|
||||||
setPattern(_pattern);
|
setPattern(_pattern);
|
||||||
@ -34,6 +47,7 @@ function useStrudel({ defaultOutput, interval, getTime, evalOnMount = false, ini
|
|||||||
if (autolink) {
|
if (autolink) {
|
||||||
window.location.hash = '#' + encodeURIComponent(btoa(code));
|
window.location.hash = '#' + encodeURIComponent(btoa(code));
|
||||||
}
|
}
|
||||||
|
afterEval?.();
|
||||||
},
|
},
|
||||||
onToggle: (v) => setStarted(v),
|
onToggle: (v) => setStarted(v),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -11,25 +11,32 @@ export default createTheme({
|
|||||||
lineHighlight: '#8a91991a',
|
lineHighlight: '#8a91991a',
|
||||||
gutterBackground: 'transparent',
|
gutterBackground: 'transparent',
|
||||||
// gutterForeground: '#8a919966',
|
// gutterForeground: '#8a919966',
|
||||||
gutterForeground: '#676e95',
|
gutterForeground: '#8a919966',
|
||||||
},
|
},
|
||||||
styles: [
|
styles: [
|
||||||
{ tag: t.keyword, color: '#c792ea' },
|
{ tag: t.keyword, color: '#c792ea' },
|
||||||
{ tag: t.operator, color: '#89ddff' },
|
{ tag: t.operator, color: '#89ddff' },
|
||||||
{ tag: t.special(t.variableName), color: '#eeffff' },
|
{ tag: t.special(t.variableName), color: '#eeffff' },
|
||||||
{ tag: t.typeName, color: '#f07178' },
|
// { tag: t.typeName, color: '#f07178' }, // original
|
||||||
|
{ tag: t.typeName, color: '#c3e88d' },
|
||||||
{ tag: t.atom, color: '#f78c6c' },
|
{ tag: t.atom, color: '#f78c6c' },
|
||||||
{ tag: t.number, color: '#ff5370' },
|
// { tag: t.number, color: '#ff5370' }, // original
|
||||||
|
{ tag: t.number, color: '#c3e88d' },
|
||||||
{ tag: t.definition(t.variableName), color: '#82aaff' },
|
{ tag: t.definition(t.variableName), color: '#82aaff' },
|
||||||
{ tag: t.string, color: '#c3e88d' },
|
{ tag: t.string, color: '#c3e88d' },
|
||||||
{ tag: t.special(t.string), color: '#f07178' },
|
// { tag: t.special(t.string), color: '#f07178' }, // original
|
||||||
|
{ tag: t.special(t.string), color: '#c3e88d' },
|
||||||
{ tag: t.comment, color: '#7d8799' },
|
{ tag: t.comment, color: '#7d8799' },
|
||||||
{ tag: t.variableName, color: '#f07178' },
|
// { tag: t.variableName, color: '#f07178' }, // original
|
||||||
{ tag: t.tagName, color: '#ff5370' },
|
{ tag: t.variableName, color: '#c792ea' },
|
||||||
{ tag: t.bracket, color: '#a2a1a4' },
|
// { tag: t.tagName, color: '#ff5370' }, // original
|
||||||
|
{ tag: t.tagName, color: '#c3e88d' },
|
||||||
|
{ tag: t.bracket, color: '#525154' },
|
||||||
|
// { tag: t.bracket, color: '#a2a1a4' }, // original
|
||||||
{ tag: t.meta, color: '#ffcb6b' },
|
{ tag: t.meta, color: '#ffcb6b' },
|
||||||
{ tag: t.attributeName, color: '#c792ea' },
|
{ tag: t.attributeName, color: '#c792ea' },
|
||||||
{ tag: t.propertyName, color: '#c792ea' },
|
{ tag: t.propertyName, color: '#c792ea' },
|
||||||
|
|
||||||
{ tag: t.className, color: '#decb6b' },
|
{ tag: t.className, color: '#decb6b' },
|
||||||
{ tag: t.invalid, color: '#ffffff' },
|
{ tag: t.invalid, color: '#ffffff' },
|
||||||
],
|
],
|
||||||
|
|||||||
15
repl/package-lock.json
generated
15
repl/package-lock.json
generated
@ -8,6 +8,7 @@
|
|||||||
"name": "@strudel.cycles/repl",
|
"name": "@strudel.cycles/repl",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@heroicons/react": "^2.0.13",
|
||||||
"@supabase/supabase-js": "^1.35.3",
|
"@supabase/supabase-js": "^1.35.3",
|
||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
@ -449,6 +450,14 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@heroicons/react": {
|
||||||
|
"version": "2.0.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.0.13.tgz",
|
||||||
|
"integrity": "sha512-iSN5XwmagrnirWlYEWNPdCDj9aRYVD/lnK3JlsC9/+fqGF80k8C7rl+1HCvBX0dBoagKqOFBs6fMhJJ1hOg1EQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">= 16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
|
||||||
@ -2972,6 +2981,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"@heroicons/react": {
|
||||||
|
"version": "2.0.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.0.13.tgz",
|
||||||
|
"integrity": "sha512-iSN5XwmagrnirWlYEWNPdCDj9aRYVD/lnK3JlsC9/+fqGF80k8C7rl+1HCvBX0dBoagKqOFBs6fMhJJ1hOg1EQ==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"@jridgewell/gen-mapping": {
|
"@jridgewell/gen-mapping": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
"dbdump": "node src/test/dbdump.js > src/test/dbdump.json"
|
"dbdump": "node src/test/dbdump.js > src/test/dbdump.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@heroicons/react": "^2.0.13",
|
||||||
"@supabase/supabase-js": "^1.35.3",
|
"@supabase/supabase-js": "^1.35.3",
|
||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
|
|||||||
@ -18,3 +18,18 @@ body {
|
|||||||
background: black;
|
background: black;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cm-gutters {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-content {
|
||||||
|
padding-top: 12px !important;
|
||||||
|
padding-left: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
/* font-family: 'monospace'; */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
300
repl/src/App.jsx
300
repl/src/App.jsx
@ -19,6 +19,12 @@ import { createClient } from '@supabase/supabase-js';
|
|||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { useStrudel } from '@strudel.cycles/react';
|
import { useStrudel } from '@strudel.cycles/react';
|
||||||
import { webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
import { webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
||||||
|
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
||||||
|
import StopCircleIcon from '@heroicons/react/20/solid/StopCircleIcon';
|
||||||
|
import CommandLineIcon from '@heroicons/react/20/solid/CommandLineIcon';
|
||||||
|
import SparklesIcon from '@heroicons/react/20/solid/SparklesIcon';
|
||||||
|
import LinkIcon from '@heroicons/react/20/solid/LinkIcon';
|
||||||
|
import AcademicCapIcon from '@heroicons/react/20/solid/AcademicCapIcon';
|
||||||
|
|
||||||
initAudioOnFirstClick();
|
initAudioOnFirstClick();
|
||||||
|
|
||||||
@ -46,8 +52,6 @@ evalScope(
|
|||||||
|
|
||||||
prebake();
|
prebake();
|
||||||
|
|
||||||
const pushLog = (message) =>
|
|
||||||
logger(`%c${message}`, 'background-color: black;color:white;padding:4px;border-radius:15px');
|
|
||||||
const hideHeader = false;
|
const hideHeader = false;
|
||||||
const pending = false;
|
const pending = false;
|
||||||
const getTime = () => getAudioContext().currentTime;
|
const getTime = () => getAudioContext().currentTime;
|
||||||
@ -83,48 +87,64 @@ async function initCode() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getRandomTune() {
|
function getRandomTune() {
|
||||||
const allTunes = Object.values(tunes);
|
const allTunes = Object.entries(tunes);
|
||||||
const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
||||||
return randomItem(allTunes);
|
const [name, code] = randomItem(allTunes);
|
||||||
|
return { name, code };
|
||||||
}
|
}
|
||||||
|
|
||||||
const randomTune = getRandomTune();
|
const { code: randomTune, name } = getRandomTune();
|
||||||
const isEmbedded = window.location !== window.parent.location;
|
const isEmbedded = window.location !== window.parent.location;
|
||||||
function App() {
|
function App() {
|
||||||
// const [editor, setEditor] = useState();
|
// const [editor, setEditor] = useState();
|
||||||
const [view, setView] = useState();
|
const [view, setView] = useState();
|
||||||
const [lastShared, setLastShared] = useState();
|
const [lastShared, setLastShared] = useState();
|
||||||
|
|
||||||
const {
|
// logger
|
||||||
code,
|
const [log, setLog] = useState([]);
|
||||||
setCode,
|
const pushLog = (message, type) => {
|
||||||
scheduler,
|
setLog((l) => {
|
||||||
evaluate,
|
logger(message);
|
||||||
activateCode,
|
const lastLog = l.length ? l[l.length - 1] : undefined;
|
||||||
error,
|
const index = (lastLog?.index ?? -1) + 1;
|
||||||
isDirty,
|
if (lastLog && lastLog.message === message) {
|
||||||
activeCode,
|
l = l.slice(0, -1).concat([{ message, type, count: (lastLog.count ?? 1) + 1, index }]);
|
||||||
pattern,
|
} else {
|
||||||
started,
|
l = l.concat([{ message, type, index }]);
|
||||||
togglePlay,
|
}
|
||||||
stop,
|
return l.slice(-20);
|
||||||
} = useStrudel({
|
});
|
||||||
initialCode: '// LOADING',
|
};
|
||||||
defaultOutput: webaudioOutput,
|
|
||||||
getTime,
|
|
||||||
autolink: true,
|
|
||||||
});
|
|
||||||
useEffect(() => {
|
|
||||||
initCode().then((decoded) => setCode(decoded || randomTune));
|
|
||||||
}, []);
|
|
||||||
const logBox = useRef();
|
const logBox = useRef();
|
||||||
// scroll log box to bottom when log changes
|
useLayoutEffect(() => {
|
||||||
|
|
||||||
/* useLayoutEffect(() => {
|
|
||||||
if (logBox.current) {
|
if (logBox.current) {
|
||||||
|
// scroll log box to bottom when log changes
|
||||||
logBox.current.scrollTop = logBox.current?.scrollHeight;
|
logBox.current.scrollTop = logBox.current?.scrollHeight;
|
||||||
}
|
}
|
||||||
}, [log]); */
|
}, [log]);
|
||||||
|
|
||||||
|
// repl
|
||||||
|
const { code, setCode, scheduler, evaluate, activateCode, error, isDirty, activeCode, pattern, started, stop } =
|
||||||
|
useStrudel({
|
||||||
|
initialCode: '// LOADING',
|
||||||
|
defaultOutput: webaudioOutput,
|
||||||
|
getTime,
|
||||||
|
autolink: true,
|
||||||
|
onLog: pushLog,
|
||||||
|
});
|
||||||
|
|
||||||
|
// init code
|
||||||
|
useEffect(() => {
|
||||||
|
initCode().then((decoded) => {
|
||||||
|
pushLog(
|
||||||
|
`🌀 Welcome to Strudel! ${
|
||||||
|
decoded ? `Code was decoded from the URL` : `A random code snippet named "${name}" has been loaded!`
|
||||||
|
} Press play or hit ctrl+enter to listen!`,
|
||||||
|
'info',
|
||||||
|
);
|
||||||
|
setCode(decoded || randomTune);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
// set active pattern on ctrl+enter
|
// set active pattern on ctrl+enter
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@ -154,47 +174,47 @@ function App() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col">
|
<div className="h-screen flex flex-col">
|
||||||
{!hideHeader && (
|
{!hideHeader && (
|
||||||
<header
|
<header
|
||||||
id="header"
|
id="header"
|
||||||
className={cx(
|
className={cx(
|
||||||
'flex-none w-full px-2 flex border-b border-gray-200 justify-between z-[10] bg-gray-100',
|
'flex-none w-full md:flex text-black shadow-lg justify-between z-[100] text-lg bg-linegray select-none sticky top-0',
|
||||||
isEmbedded ? 'h-8' : 'h-14',
|
isEmbedded ? 'h-12 md:h-8' : 'h-25 md:h-14',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="px-2 flex items-center space-x-2 pt-2 md:pt-0 pointer-events-none">
|
||||||
<img src={logo} className={cx('Tidal-logo', isEmbedded ? 'w-6 h-6' : 'w-10 h-10')} alt="logo" />
|
<img
|
||||||
<h1 className={isEmbedded ? 'text-l' : 'text-xl'}>Strudel {isEmbedded ? 'Mini ' : ''}REPL</h1>
|
src={logo}
|
||||||
|
className={cx('Tidal-logo', isEmbedded ? 'w-8 h-8' : 'w-10 h-10')} // 'bg-[#ffffff80] rounded-full'
|
||||||
|
alt="logo"
|
||||||
|
/>
|
||||||
|
<h1
|
||||||
|
className={cx(
|
||||||
|
isEmbedded ? 'text-l' : 'text-xl',
|
||||||
|
// 'bg-clip-text bg-gradient-to-r from-primary to-secondary text-transparent font-bold',
|
||||||
|
'text-white font-bold',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
strudel <span className="text-sm">REPL</span>
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex max-w-full overflow-auto text-white ">
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await getAudioContext().resume(); // fixes no sound in ios webkit
|
await getAudioContext().resume(); // fixes no sound in ios webkit
|
||||||
togglePlay();
|
if (!started) {
|
||||||
|
activateCode();
|
||||||
|
} else {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
className={cx('hover:bg-gray-300', !isEmbedded ? 'p-2' : 'px-2')}
|
className={cx(!isEmbedded ? 'p-2' : 'px-2')}
|
||||||
>
|
>
|
||||||
{!pending ? (
|
{!pending ? (
|
||||||
<span className={cx('flex items-center', isEmbedded ? 'w-16' : 'w-16')}>
|
<span className={cx('flex items-center space-x-1 hover:text-primary', isEmbedded ? 'w-16' : 'w-16')}>
|
||||||
{started ? (
|
{started ? <StopCircleIcon className="w-5 h-5" /> : <PlayCircleIcon className="w-5 h-5" />}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
<span>{started ? 'stop' : 'play'}</span>
|
||||||
<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>
|
|
||||||
) : (
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 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>
|
|
||||||
)}
|
|
||||||
{started ? 'pause' : 'play'}
|
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<>loading...</>
|
<>loading...</>
|
||||||
@ -206,37 +226,48 @@ function App() {
|
|||||||
pushLog('Code updated! Tip: You can also update the code by pressing ctrl+enter.');
|
pushLog('Code updated! Tip: You can also update the code by pressing ctrl+enter.');
|
||||||
}}
|
}}
|
||||||
className={cx(
|
className={cx(
|
||||||
'hover:bg-gray-300',
|
'flex items-center space-x-1',
|
||||||
!isEmbedded ? 'p-2' : 'px-2',
|
!isEmbedded ? 'p-2' : 'px-2',
|
||||||
!isDirty || !activeCode ? 'opacity-50' : '',
|
!isDirty || !activeCode ? 'opacity-50' : 'hover:text-primary',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
🔄 update
|
<CommandLineIcon className="w-5 h-5" />
|
||||||
|
<span>update</span>
|
||||||
</button>
|
</button>
|
||||||
{!isEmbedded && (
|
{!isEmbedded && (
|
||||||
<button
|
<button
|
||||||
className="hover:bg-gray-300 p-2"
|
className="hover:text-primary p-2 flex items-center space-x-1"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const _code = getRandomTune();
|
const { code, name } = getRandomTune();
|
||||||
|
pushLog(`✨ loading random tune "${name}"`);
|
||||||
/*
|
/*
|
||||||
cleanupDraw();
|
cleanupDraw();
|
||||||
cleanupUi(); */
|
cleanupUi(); */
|
||||||
resetLoadedSamples();
|
resetLoadedSamples();
|
||||||
await prebake(); // declare default samples
|
await prebake(); // declare default samples
|
||||||
await evaluate(_code, false);
|
await evaluate(code, false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
🎲 random
|
<SparklesIcon className="w-5 h-5" />
|
||||||
|
<span> shuffle</span>
|
||||||
|
{/* <MusicalNoteIcon /> <RadioIcon/> */}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{!isEmbedded && (
|
{!isEmbedded && (
|
||||||
<button className={cx('hover:bg-gray-300', !isEmbedded ? 'p-2' : 'px-2')}>
|
<a
|
||||||
<a href="./tutorial">📚 tutorial</a>
|
href="./tutorial"
|
||||||
</button>
|
className={cx('hover:text-primary flex items-center space-x-1', !isEmbedded ? 'p-2' : 'px-2')}
|
||||||
|
>
|
||||||
|
<AcademicCapIcon className="w-5 h-5" />
|
||||||
|
<span>learn</span>
|
||||||
|
</a>
|
||||||
)}
|
)}
|
||||||
{!isEmbedded && (
|
{!isEmbedded && (
|
||||||
<button
|
<button
|
||||||
className={cx('cursor-pointer hover:bg-gray-300', !isEmbedded ? 'p-2' : 'px-2')}
|
className={cx(
|
||||||
|
'cursor-pointer hover:text-primary flex items-center space-x-1',
|
||||||
|
!isEmbedded ? 'p-2' : 'px-2',
|
||||||
|
)}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const codeToShare = activeCode || code;
|
const codeToShare = activeCode || code;
|
||||||
if (lastShared === codeToShare) {
|
if (lastShared === codeToShare) {
|
||||||
@ -263,18 +294,20 @@ function App() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
📣 share{lastShared && lastShared === (activeCode || code) ? 'd!' : ''}
|
<LinkIcon className="w-5 h-5" />
|
||||||
|
<span>share{lastShared && lastShared === (activeCode || code) ? 'd!' : ''}</span>
|
||||||
|
{/* GlobaAlt Megaphone PaperAirplane Share */}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{isEmbedded && (
|
{isEmbedded && (
|
||||||
<button className={cx('hover:bg-gray-300 px-2')}>
|
<button className={cx('hover:text-primary 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">
|
||||||
🚀 open
|
🚀 open
|
||||||
</a>
|
</a>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{isEmbedded && (
|
{isEmbedded && (
|
||||||
<button className={cx('hover:bg-gray-300 px-2')}>
|
<button className={cx('hover:text-primary px-2')}>
|
||||||
<a
|
<a
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.location.href = initialUrl;
|
window.location.href = initialUrl;
|
||||||
@ -289,45 +322,94 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
)}
|
)}
|
||||||
<section className="grow flex flex-col text-gray-100">
|
<section className="grow flex text-gray-100 relative overflow-auto cursor-text" id="code">
|
||||||
<div className="grow relative flex overflow-auto pb-8 cursor-text" id="code">
|
<CodeMirror value={code} onChange={setCode} onViewChanged={setView} />
|
||||||
{/* onCursor={markParens} */}
|
|
||||||
<CodeMirror value={code} onChange={setCode} onViewChanged={setView} />
|
|
||||||
<span className="z-[20] bg-black rounded-t-md py-1 px-2 fixed bottom-0 right-1 text-xs whitespace-pre text-right pointer-events-none">
|
|
||||||
{!started
|
|
||||||
? `press ctrl+enter to play\n`
|
|
||||||
: isDirty
|
|
||||||
? `press ctrl+enter to update\n`
|
|
||||||
: 'press ctrl+dot do stop\n'}
|
|
||||||
</span>
|
|
||||||
{error && (
|
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
'rounded-md fixed pointer-events-none left-2 bottom-1 text-xs bg-black px-2 z-[20]',
|
|
||||||
'text-red-500',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{error?.message || 'unknown error'}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{/* !isEmbedded && !hideConsole && (
|
|
||||||
<textarea
|
|
||||||
className="z-[10] h-16 border-0 text-xs bg-[transparent] border-t border-slate-600 resize-none"
|
|
||||||
value={log}
|
|
||||||
readOnly
|
|
||||||
ref={logBox}
|
|
||||||
style={{ fontFamily: 'monospace' }}
|
|
||||||
/>
|
|
||||||
) */}
|
|
||||||
</section>
|
</section>
|
||||||
{/* !isEmbedded && (
|
<footer className="bg-linegray">
|
||||||
<button className="fixed right-4 bottom-2 z-[11]" onClick={() => playStatic(code)}>
|
{/* {error && (
|
||||||
static
|
<div
|
||||||
</button>
|
className={cx(
|
||||||
) */}
|
'rounded-md pointer-events-none left-0 p-1 text-sm bg-black px-2 z-[20] max-w-screen break-all',
|
||||||
|
'text-red-500',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{error?.message || 'unknown error'}
|
||||||
|
</div>
|
||||||
|
)} */}
|
||||||
|
<div
|
||||||
|
ref={logBox}
|
||||||
|
className="text-white font-mono text-sm h-32 flex-none overflow-auto max-w-full break-all p-2"
|
||||||
|
>
|
||||||
|
{log.map((l, i) => (
|
||||||
|
<div
|
||||||
|
key={l.index}
|
||||||
|
className={cx(l.type === 'error' && 'text-red-500', l.type === 'info' && 'text-secondary')}
|
||||||
|
>
|
||||||
|
{l.index}: {l.message}
|
||||||
|
{l.count ? ` (${l.count})` : ''}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
||||||
|
function ActionButton({ children, onClick, className }) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cx(
|
||||||
|
'bg-lineblack py-1 px-2 bottom-0 text-md whitespace-pre text-right pb-2 cursor-pointer flex items-center space-x-1 hover:text-primary',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function FloatingBottomMenu() {
|
||||||
|
{
|
||||||
|
/* <span className="hidden md:block z-[20] bg-black py-1 px-2 text-sm absolute bottom-1 right-0 text-md whitespace-pre text-right pointer-events-none pb-2">
|
||||||
|
{!started
|
||||||
|
? `press ctrl+enter to play\n`
|
||||||
|
: isDirty
|
||||||
|
? `press ctrl+enter to update\n`
|
||||||
|
: 'press ctrl+dot do stop\n'}
|
||||||
|
</span>*/
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center w-full absolute bottom-0 z-[20]">
|
||||||
|
<ActionButton
|
||||||
|
onClick={async () => {
|
||||||
|
await getAudioContext().resume(); // fixes no sound in ios webkit
|
||||||
|
if (!started) {
|
||||||
|
activateCode();
|
||||||
|
} else {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!pending ? (
|
||||||
|
<span className={cx('flex items-center space-x-1 hover:text-primary', isEmbedded ? 'w-16' : 'w-16')}>
|
||||||
|
{started ? <StopCircleIcon className="w-5 h-5" /> : <PlayCircleIcon className="w-5 h-5" />}
|
||||||
|
<span>{started ? 'stop' : 'play'}</span>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<>loading...</>
|
||||||
|
)}
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
onClick={() => {
|
||||||
|
isDirty && activateCode();
|
||||||
|
}}
|
||||||
|
className={cx(!isDirty || !activeCode ? 'opacity-50 hover:text-inherit' : 'hover:text-primary')}
|
||||||
|
>
|
||||||
|
<CommandLineIcon className="w-5 h-5" />
|
||||||
|
<span>update</span>
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -6,9 +6,18 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// TODO: find out if leaving out tutorial path works now
|
// TODO: find out if leaving out tutorial path works now
|
||||||
content: ['./src/**/*.{js,jsx,ts,tsx}','./tutorial/**/*.{js,jsx,ts,tsx}'],
|
content: ['./src/**/*.{js,jsx,ts,tsx}', './tutorial/**/*.{js,jsx,ts,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
|
colors: {
|
||||||
|
primary: '#c792ea',
|
||||||
|
secondary: '#c3e88d',
|
||||||
|
tertiary: '#82aaff',
|
||||||
|
highlight: '#ffcc00',
|
||||||
|
linegray: '#8a91991a',
|
||||||
|
lineblack: '#00000095',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [require('@tailwindcss/typography')],
|
plugins: [require('@tailwindcss/typography')],
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user