mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 05:38:35 +00:00
Merge branch 'main' into jade/uwu2000
This commit is contained in:
commit
d9c3f051b9
@ -31,6 +31,10 @@ This project is organized into many [packages](./packages), which are also avail
|
||||
|
||||
Read more about how to use these in your own project [here](https://strudel.cc/technical-manual/project-start).
|
||||
|
||||
You will need to abide by the terms of the [GNU Affero Public Licence v3](LICENSE.md). As such, Strudel code can only be shared within free/open source projects under the same license -- see the license for details.
|
||||
|
||||
Licensing info for the default sound banks can be found over on the [dough-samples](https://github.com/felixroos/dough-samples/blob/main/README.md) repository.
|
||||
|
||||
## Contributing
|
||||
|
||||
There are many ways to contribute to this project! See [contribution guide](./CONTRIBUTING.md).
|
||||
|
||||
@ -59,6 +59,7 @@
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.19.0",
|
||||
"@tauri-apps/cli": "^2.2.7",
|
||||
"@vitest/coverage-v8": "3.0.4",
|
||||
"@vitest/ui": "^3.0.4",
|
||||
"acorn": "^8.14.0",
|
||||
"dependency-tree": "^11.0.1",
|
||||
|
||||
@ -1244,6 +1244,16 @@ export function reify(thing) {
|
||||
return pure(thing);
|
||||
}
|
||||
|
||||
/** Takes a list of patterns, and returns a pattern of lists.
|
||||
*/
|
||||
export function sequenceP(pats) {
|
||||
let result = pure([]);
|
||||
for (const pat of pats) {
|
||||
result = result.bind((list) => pat.fmap((v) => list.concat([v])));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** The given items are played at the same time at the same length.
|
||||
*
|
||||
* @return {Pattern}
|
||||
|
||||
@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
import { Hap } from './hap.mjs';
|
||||
import { Pattern, fastcat, pure, register, reify, silence, stack } from './pattern.mjs';
|
||||
import { Pattern, fastcat, pure, register, reify, silence, stack, sequenceP } from './pattern.mjs';
|
||||
import Fraction from './fraction.mjs';
|
||||
|
||||
import { id, keyAlias, getCurrentKeyboardState } from './util.mjs';
|
||||
@ -433,19 +433,30 @@ export const chooseCycles = (...xs) => chooseInWith(rand.segment(1), xs);
|
||||
export const randcat = chooseCycles;
|
||||
|
||||
const _wchooseWith = function (pat, ...pairs) {
|
||||
// A list of patterns of values
|
||||
const values = pairs.map((pair) => reify(pair[0]));
|
||||
|
||||
// A list of weight patterns
|
||||
const weights = [];
|
||||
let accum = 0;
|
||||
|
||||
let total = pure(0);
|
||||
for (const pair of pairs) {
|
||||
accum += pair[1];
|
||||
weights.push(accum);
|
||||
// 'add' accepts either values or patterns of values here, so no need
|
||||
// to explicitly reify
|
||||
total = total.add(pair[1]);
|
||||
// accumulate our list of weight patterns
|
||||
weights.push(total);
|
||||
}
|
||||
const total = accum;
|
||||
// a pattern of lists of weights
|
||||
const weightspat = sequenceP(weights);
|
||||
|
||||
// Takes a number from 0-1, returns a pattern of patterns of values
|
||||
const match = function (r) {
|
||||
const find = r * total;
|
||||
return values[weights.findIndex((x) => x > find, weights)];
|
||||
const findpat = total.mul(r);
|
||||
return weightspat.fmap((weights) => (find) => values[weights.findIndex((x) => x > find, weights)]).appLeft(findpat);
|
||||
};
|
||||
return pat.fmap(match);
|
||||
// This returns a pattern of patterns.. The innerJoin is in wchooseCycles
|
||||
return pat.bind(match);
|
||||
};
|
||||
|
||||
const wchooseWith = (...args) => _wchooseWith(...args).outerJoin();
|
||||
@ -467,6 +478,9 @@ export const wchoose = (...pairs) => wchooseWith(rand, ...pairs);
|
||||
* wchooseCycles(["bd",10], ["hh",1], ["sd",1]).s().fast(8)
|
||||
* @example
|
||||
* wchooseCycles(["bd bd bd",5], ["hh hh hh",3], ["sd sd sd",1]).fast(4).s()
|
||||
* @example
|
||||
* // The probability can itself be a pattern
|
||||
* wchooseCycles(["bd(3,8)","<5 0>"], ["hh hh hh",3]).fast(4).s()
|
||||
*/
|
||||
export const wchooseCycles = (...pairs) => _wchooseWith(rand.segment(1), ...pairs).innerJoin();
|
||||
|
||||
|
||||
95
pnpm-lock.yaml
generated
95
pnpm-lock.yaml
generated
@ -39,6 +39,9 @@ importers:
|
||||
'@tauri-apps/cli':
|
||||
specifier: ^2.2.7
|
||||
version: 2.2.7
|
||||
'@vitest/coverage-v8':
|
||||
specifier: 3.0.4
|
||||
version: 3.0.4(vitest@3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0))
|
||||
'@vitest/ui':
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(vitest@3.0.4)
|
||||
@ -1412,6 +1415,10 @@ packages:
|
||||
resolution: {integrity: sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2':
|
||||
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@codemirror/autocomplete@6.18.4':
|
||||
resolution: {integrity: sha512-sFAphGQIqyQZfP2ZBsSHV7xQvo9Py0rV0dW7W3IMRdS+zDuNb2l3no78CvUaWKGfzFjI4FTrLdUSj86IGb2hRA==}
|
||||
|
||||
@ -1834,6 +1841,10 @@ packages:
|
||||
'@isaacs/string-locale-compare@1.1.0':
|
||||
resolution: {integrity: sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==}
|
||||
|
||||
'@istanbuljs/schema@0.1.3':
|
||||
resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
'@jest/schemas@29.6.3':
|
||||
resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
@ -2818,6 +2829,15 @@ packages:
|
||||
peerDependencies:
|
||||
vite: ^4.2.0 || ^5.0.0 || ^6.0.0
|
||||
|
||||
'@vitest/coverage-v8@3.0.4':
|
||||
resolution: {integrity: sha512-f0twgRCHgbs24Dp8cLWagzcObXMcuKtAwgxjJV/nnysPAJJk1JiKu/W0gIehZLmkljhJXU/E0/dmuQzsA/4jhA==}
|
||||
peerDependencies:
|
||||
'@vitest/browser': 3.0.4
|
||||
vitest: 3.0.4
|
||||
peerDependenciesMeta:
|
||||
'@vitest/browser':
|
||||
optional: true
|
||||
|
||||
'@vitest/expect@3.0.4':
|
||||
resolution: {integrity: sha512-Nm5kJmYw6P2BxhJPkO3eKKhGYKRsnqJqf+r0yOGRKpEP+bSCBDsjXgiu1/5QFrnPMEgzfC38ZEjvCFgaNBC0Eg==}
|
||||
|
||||
@ -4487,6 +4507,9 @@ packages:
|
||||
hs2js@0.1.0:
|
||||
resolution: {integrity: sha512-THlUIMX8tZf6gtbz5RUZ8xQUyKJEItsx7bxEBcouFIEWjeo90376WMocj3JEz6qTv5nM+tjo3vNvLf89XruMvg==}
|
||||
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
html-escaper@3.0.3:
|
||||
resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==}
|
||||
|
||||
@ -4837,6 +4860,22 @@ packages:
|
||||
resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
istanbul-lib-coverage@3.2.2:
|
||||
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
istanbul-lib-report@3.0.1:
|
||||
resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
istanbul-lib-source-maps@5.0.6:
|
||||
resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
istanbul-reports@3.1.7:
|
||||
resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
jackspeak@3.4.3:
|
||||
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
|
||||
|
||||
@ -6887,6 +6926,10 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
test-exclude@7.0.1:
|
||||
resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
text-encoding-shim@1.0.5:
|
||||
resolution: {integrity: sha512-H7yYW+jRn4yhu60ygZ2f/eMhXPITRt4QSUTKzLm+eCaDsdX8avmgWpmtmHAzesjBVUTAypz9odu5RKUjX5HNYA==}
|
||||
|
||||
@ -7505,6 +7548,7 @@ packages:
|
||||
|
||||
workbox-google-analytics@7.0.0:
|
||||
resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==}
|
||||
deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained
|
||||
|
||||
workbox-navigation-preload@7.0.0:
|
||||
resolution: {integrity: sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==}
|
||||
@ -8568,6 +8612,8 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2': {}
|
||||
|
||||
'@codemirror/autocomplete@6.18.4':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.10.8
|
||||
@ -8934,6 +8980,8 @@ snapshots:
|
||||
|
||||
'@isaacs/string-locale-compare@1.1.0': {}
|
||||
|
||||
'@istanbuljs/schema@0.1.3': {}
|
||||
|
||||
'@jest/schemas@29.6.3':
|
||||
dependencies:
|
||||
'@sinclair/typebox': 0.27.8
|
||||
@ -10187,6 +10235,24 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/coverage-v8@3.0.4(vitest@3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0))':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
debug: 4.4.0
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
istanbul-lib-source-maps: 5.0.6
|
||||
istanbul-reports: 3.1.7
|
||||
magic-string: 0.30.17
|
||||
magicast: 0.3.5
|
||||
std-env: 3.8.0
|
||||
test-exclude: 7.0.1
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/expect@3.0.4':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.0.4
|
||||
@ -12202,6 +12268,8 @@ snapshots:
|
||||
dependencies:
|
||||
web-tree-sitter: 0.20.8
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
html-escaper@3.0.3: {}
|
||||
|
||||
html-void-elements@3.0.0: {}
|
||||
@ -12540,6 +12608,27 @@ snapshots:
|
||||
|
||||
isobject@3.0.1: {}
|
||||
|
||||
istanbul-lib-coverage@3.2.2: {}
|
||||
|
||||
istanbul-lib-report@3.0.1:
|
||||
dependencies:
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
make-dir: 4.0.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
istanbul-lib-source-maps@5.0.6:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
debug: 4.4.0
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
istanbul-reports@3.1.7:
|
||||
dependencies:
|
||||
html-escaper: 2.0.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
|
||||
jackspeak@3.4.3:
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
@ -15262,6 +15351,12 @@ snapshots:
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
|
||||
test-exclude@7.0.1:
|
||||
dependencies:
|
||||
'@istanbuljs/schema': 0.1.3
|
||||
glob: 10.4.5
|
||||
minimatch: 9.0.5
|
||||
|
||||
text-encoding-shim@1.0.5: {}
|
||||
|
||||
text-extensions@1.9.0: {}
|
||||
|
||||
@ -9687,6 +9687,59 @@ exports[`runs examples > example "wchooseCycles" example index 1 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "wchooseCycles" example index 2 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/32 | s:bd ]",
|
||||
"[ 3/32 → 1/8 | s:bd ]",
|
||||
"[ 3/16 → 7/32 | s:bd ]",
|
||||
"[ 1/4 → 1/3 | s:hh ]",
|
||||
"[ 1/3 → 5/12 | s:hh ]",
|
||||
"[ 5/12 → 1/2 | s:hh ]",
|
||||
"[ 1/2 → 7/12 | s:hh ]",
|
||||
"[ 7/12 → 2/3 | s:hh ]",
|
||||
"[ 2/3 → 3/4 | s:hh ]",
|
||||
"[ 3/4 → 5/6 | s:hh ]",
|
||||
"[ 5/6 → 11/12 | s:hh ]",
|
||||
"[ 11/12 → 1/1 | s:hh ]",
|
||||
"[ 1/1 → 33/32 | s:bd ]",
|
||||
"[ 35/32 → 9/8 | s:bd ]",
|
||||
"[ 19/16 → 39/32 | s:bd ]",
|
||||
"[ 5/4 → 4/3 | s:hh ]",
|
||||
"[ 4/3 → 17/12 | s:hh ]",
|
||||
"[ 17/12 → 3/2 | s:hh ]",
|
||||
"[ 3/2 → 49/32 | s:bd ]",
|
||||
"[ 51/32 → 13/8 | s:bd ]",
|
||||
"[ 27/16 → 55/32 | s:bd ]",
|
||||
"[ 7/4 → 11/6 | s:hh ]",
|
||||
"[ 11/6 → 23/12 | s:hh ]",
|
||||
"[ 23/12 → 2/1 | s:hh ]",
|
||||
"[ 2/1 → 25/12 | s:hh ]",
|
||||
"[ 25/12 → 13/6 | s:hh ]",
|
||||
"[ 13/6 → 9/4 | s:hh ]",
|
||||
"[ 9/4 → 7/3 | s:hh ]",
|
||||
"[ 7/3 → 29/12 | s:hh ]",
|
||||
"[ 29/12 → 5/2 | s:hh ]",
|
||||
"[ 5/2 → 81/32 | s:bd ]",
|
||||
"[ 83/32 → 21/8 | s:bd ]",
|
||||
"[ 43/16 → 87/32 | s:bd ]",
|
||||
"[ 11/4 → 17/6 | s:hh ]",
|
||||
"[ 17/6 → 35/12 | s:hh ]",
|
||||
"[ 35/12 → 3/1 | s:hh ]",
|
||||
"[ 3/1 → 97/32 | s:bd ]",
|
||||
"[ 99/32 → 25/8 | s:bd ]",
|
||||
"[ 51/16 → 103/32 | s:bd ]",
|
||||
"[ 13/4 → 10/3 | s:hh ]",
|
||||
"[ 10/3 → 41/12 | s:hh ]",
|
||||
"[ 41/12 → 7/2 | s:hh ]",
|
||||
"[ 7/2 → 43/12 | s:hh ]",
|
||||
"[ 43/12 → 11/3 | s:hh ]",
|
||||
"[ 11/3 → 15/4 | s:hh ]",
|
||||
"[ 15/4 → 23/6 | s:hh ]",
|
||||
"[ 23/6 → 47/12 | s:hh ]",
|
||||
"[ 47/12 → 4/1 | s:hh ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "when" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/3 | note:c3 ]",
|
||||
|
||||
@ -36,8 +36,6 @@ export function Showcase() {
|
||||
}
|
||||
|
||||
let _videos = [
|
||||
{ title: 'Coding Music With Strudel Workhop by Dan Gorelick and Viola He', id: 'oqyAJ4WeKoU' },
|
||||
{ title: 'Hexe - playing w strudel live coding music', id: '03m3F5xVOMg' },
|
||||
{ title: 'DJ_Dave - Array [Lil Data Edit]', id: 'KUujFuTcuKc' },
|
||||
{ title: 'DJ_Dave - Bitrot [v10101a Edit]', id: 'z_cJMdBp67Q' },
|
||||
{ title: 'you will not steve reich your way out of it', id: 'xpILnXcWyuo' },
|
||||
@ -58,7 +56,6 @@ let _videos = [
|
||||
},
|
||||
{ title: 'letSeaTstrudeL @ solstice stream 2023', id: 'fTiX6dVtdWQ' },
|
||||
{ title: 'totalgee (Glen F) @ solstice stream 2023', id: 'IvI6uaE3nLU' },
|
||||
{ title: 'Dan Gorelick @ solstice stream 2023', id: 'qMJEljJyPi0' },
|
||||
//
|
||||
/* { // not sure if this is copyrighted ...
|
||||
title: 'Creative Coding @ Chalmers University of Technology, video by svt.se',
|
||||
@ -126,6 +123,11 @@ let _videos = [
|
||||
'A first foray into combining (an early version) strudel and hydra, using flok for collaborative coding.',
|
||||
},
|
||||
{ title: 'froos @ Algorave 10th Birthday stream', id: 'IcMSocdKwvw' },
|
||||
{ title: 'todepasta 1.5', id: 'gCwaVu1Mijg' },
|
||||
{ title: 'Djenerative Music by Bogdan Vera @ TOPLAP solstice Dec 2024', id: 'LtMX4Lr1nzY' },
|
||||
{ title: 'La musique by BuboBubo @ TOPLAP solstice Dec 2024', id: 'Oz00Y_f80wU' },
|
||||
{ title: 'Livecode and vocal breaks by Switch Angel @ TOPLAP solstice Dec 2024', id: '2kzjOIsL6CM' },
|
||||
{ title: 'Eddyflux algorave set @ rudolf5', id: 'MXz8131Ut0A' },
|
||||
];
|
||||
|
||||
_shuffled = shuffleArray(_videos);
|
||||
|
||||
67
website/src/repl/components/incrementor/Incrementor.jsx
Normal file
67
website/src/repl/components/incrementor/Incrementor.jsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { Textbox } from '../textbox/Textbox';
|
||||
import cx from '@src/cx.mjs';
|
||||
|
||||
function IncButton({ children, className, ...buttonProps }) {
|
||||
return (
|
||||
<button
|
||||
tabIndex={-1}
|
||||
className={cx(
|
||||
'border border-transparent p-1 text-center text-sm transition-all hover:bg-foreground active:bg-lineBackground disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none',
|
||||
className,
|
||||
)}
|
||||
type="button"
|
||||
{...buttonProps}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
export function Incrementor({
|
||||
onChange,
|
||||
value,
|
||||
min = -Infinity,
|
||||
max = Infinity,
|
||||
className,
|
||||
incrementLabel = 'next page',
|
||||
decrementLabel = 'prev page',
|
||||
...incrementorProps
|
||||
}) {
|
||||
value = parseInt(value);
|
||||
value = isNaN(value) ? '' : value;
|
||||
return (
|
||||
<div className={cx('w-fit bg-background relative flex items-center"> rounded-md', className)}>
|
||||
<Textbox
|
||||
min={min}
|
||||
max={max}
|
||||
onChange={(v) => {
|
||||
if (v.length && v < min) {
|
||||
return;
|
||||
}
|
||||
onChange(v);
|
||||
}}
|
||||
type="number"
|
||||
placeholder=""
|
||||
value={value}
|
||||
className="w-32 mb-0 mt-0 border-none rounded-r-none bg-transparent appearance-none [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
|
||||
{...incrementorProps}
|
||||
/>
|
||||
<div className="flex gap-1 ">
|
||||
<IncButton disabled={value <= min} onClick={() => onChange(value - 1)} aria-label={decrementLabel}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" className="w-4 h-4">
|
||||
<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
|
||||
</svg>
|
||||
</IncButton>
|
||||
<IncButton
|
||||
className="rounded-r-md"
|
||||
disabled={value >= max}
|
||||
onClick={() => onChange(value + 1)}
|
||||
aria-label={incrementLabel}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" className="w-4 h-4">
|
||||
<path d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z" />
|
||||
</svg>
|
||||
</IncButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
5
website/src/repl/components/pagination/Pagination.jsx
Normal file
5
website/src/repl/components/pagination/Pagination.jsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { Incrementor } from '../incrementor/Incrementor';
|
||||
|
||||
export function Pagination({ currPage, onPageChange, className, ...incrementorProps }) {
|
||||
return <Incrementor min={1} value={currPage} onChange={onPageChange} className={className} {...incrementorProps} />;
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
import {
|
||||
exportPatterns,
|
||||
importPatterns,
|
||||
loadAndSetFeaturedPatterns,
|
||||
loadAndSetPublicPatterns,
|
||||
patternFilterName,
|
||||
useActivePattern,
|
||||
useViewingPatternData,
|
||||
@ -12,10 +14,10 @@ import { useExamplePatterns } from '../../useExamplePatterns.jsx';
|
||||
import { parseJSON, isUdels } from '../../util.mjs';
|
||||
import { ButtonGroup } from './Forms.jsx';
|
||||
import { settingsMap, useSettings } from '../../../settings.mjs';
|
||||
|
||||
function classNames(...classes) {
|
||||
return classes.filter(Boolean).join(' ');
|
||||
}
|
||||
import { Pagination } from '../pagination/Pagination.jsx';
|
||||
import { useState } from 'react';
|
||||
import { useDebounce } from '../usedebounce.jsx';
|
||||
import cx from '@src/cx.mjs';
|
||||
|
||||
export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
|
||||
const meta = useMemo(() => getMetadata(pattern.code), [pattern]);
|
||||
@ -33,13 +35,15 @@ export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
|
||||
if (title == null) {
|
||||
title = 'unnamed';
|
||||
}
|
||||
return <>{`${pattern.id}: ${title} by ${Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous'}`}</>;
|
||||
|
||||
const author = Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous';
|
||||
return <>{`${pattern.id}: ${title} by ${author.slice(0, 100)}`.slice(0, 60)}</>;
|
||||
}
|
||||
|
||||
function PatternButton({ showOutline, onClick, pattern, showHiglight }) {
|
||||
return (
|
||||
<a
|
||||
className={classNames(
|
||||
className={cx(
|
||||
'mr-4 hover:opacity-50 cursor-pointer block',
|
||||
showOutline && 'outline outline-1',
|
||||
showHiglight && 'bg-selection',
|
||||
@ -84,82 +88,72 @@ function ActionButton({ children, onClick, label, labelIsHidden }) {
|
||||
);
|
||||
}
|
||||
|
||||
export function PatternsTab({ context }) {
|
||||
const updateCodeWindow = (context, patternData, reset = false) => {
|
||||
context.handleUpdate(patternData, reset);
|
||||
};
|
||||
|
||||
const autoResetPatternOnChange = !isUdels();
|
||||
|
||||
function UserPatterns({ context }) {
|
||||
const activePattern = useActivePattern();
|
||||
const viewingPatternStore = useViewingPatternData();
|
||||
const viewingPatternData = parseJSON(viewingPatternStore);
|
||||
|
||||
const { userPatterns, patternFilter } = useSettings();
|
||||
|
||||
const examplePatterns = useExamplePatterns();
|
||||
const collections = examplePatterns.collections;
|
||||
|
||||
const updateCodeWindow = (patternData, reset = false) => {
|
||||
context.handleUpdate(patternData, reset);
|
||||
};
|
||||
const viewingPatternID = viewingPatternData?.id;
|
||||
|
||||
const autoResetPatternOnChange = !isUdels();
|
||||
|
||||
return (
|
||||
<div className="px-4 w-full text-foreground space-y-2 flex flex-col overflow-hidden max-h-full h-full">
|
||||
<ButtonGroup
|
||||
value={patternFilter}
|
||||
onChange={(value) => settingsMap.setKey('patternFilter', value)}
|
||||
items={patternFilterName}
|
||||
></ButtonGroup>
|
||||
{patternFilter === patternFilterName.user && (
|
||||
<div>
|
||||
<div className="pr-4 space-x-4 border-b border-foreground flex max-w-full overflow-x-auto">
|
||||
<ActionButton
|
||||
label="new"
|
||||
onClick={() => {
|
||||
const { data } = userPattern.createAndAddToDB();
|
||||
updateCodeWindow(data);
|
||||
}}
|
||||
/>
|
||||
<ActionButton
|
||||
label="duplicate"
|
||||
onClick={() => {
|
||||
const { data } = userPattern.duplicate(viewingPatternData);
|
||||
updateCodeWindow(data);
|
||||
}}
|
||||
/>
|
||||
<ActionButton
|
||||
label="delete"
|
||||
onClick={() => {
|
||||
const { data } = userPattern.delete(viewingPatternID);
|
||||
updateCodeWindow({ ...data, collection: userPattern.collection });
|
||||
}}
|
||||
/>
|
||||
<label className="hover:opacity-50 cursor-pointer">
|
||||
<input
|
||||
style={{ display: 'none' }}
|
||||
type="file"
|
||||
multiple
|
||||
accept="text/plain,application/json"
|
||||
onChange={(e) => importPatterns(e.target.files)}
|
||||
/>
|
||||
import
|
||||
</label>
|
||||
<ActionButton label="export" onClick={exportPatterns} />
|
||||
<div className="flex flex-col gap-2 flex-grow overflow-hidden h-full pb-2 ">
|
||||
<div className="pr-4 space-x-4 flex max-w-full overflow-x-auto">
|
||||
<ActionButton
|
||||
label="new"
|
||||
onClick={() => {
|
||||
const { data } = userPattern.createAndAddToDB();
|
||||
updateCodeWindow(context, data);
|
||||
}}
|
||||
/>
|
||||
<ActionButton
|
||||
label="duplicate"
|
||||
onClick={() => {
|
||||
const { data } = userPattern.duplicate(viewingPatternData);
|
||||
updateCodeWindow(context, data);
|
||||
}}
|
||||
/>
|
||||
<ActionButton
|
||||
label="delete"
|
||||
onClick={() => {
|
||||
const { data } = userPattern.delete(viewingPatternID);
|
||||
updateCodeWindow(context, { ...data, collection: userPattern.collection });
|
||||
}}
|
||||
/>
|
||||
<label className="hover:opacity-50 cursor-pointer">
|
||||
<input
|
||||
style={{ display: 'none' }}
|
||||
type="file"
|
||||
multiple
|
||||
accept="text/plain,application/json"
|
||||
onChange={(e) => importPatterns(e.target.files)}
|
||||
/>
|
||||
import
|
||||
</label>
|
||||
<ActionButton label="export" onClick={exportPatterns} />
|
||||
|
||||
<ActionButton
|
||||
label="delete-all"
|
||||
onClick={() => {
|
||||
const { data } = userPattern.clearAll();
|
||||
updateCodeWindow(data);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<ActionButton
|
||||
label="delete-all"
|
||||
onClick={() => {
|
||||
const { data } = userPattern.clearAll();
|
||||
updateCodeWindow(context, data);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<section className="flex overflow-y-auto max-h-full flex-grow flex-col">
|
||||
<div className="overflow-auto h-full bg-background p-2 rounded-md">
|
||||
{patternFilter === patternFilterName.user && (
|
||||
<PatternButtons
|
||||
onClick={(id) =>
|
||||
updateCodeWindow({ ...userPatterns[id], collection: userPattern.collection }, autoResetPatternOnChange)
|
||||
updateCodeWindow(
|
||||
context,
|
||||
{ ...userPatterns[id], collection: userPattern.collection },
|
||||
autoResetPatternOnChange,
|
||||
)
|
||||
}
|
||||
patterns={userPatterns}
|
||||
started={context.started}
|
||||
@ -167,23 +161,111 @@ export function PatternsTab({ context }) {
|
||||
viewingPatternID={viewingPatternID}
|
||||
/>
|
||||
)}
|
||||
{patternFilter !== patternFilterName.user &&
|
||||
Array.from(collections.keys()).map((collection) => {
|
||||
const patterns = collections.get(collection);
|
||||
return (
|
||||
<section key={collection} className="py-2">
|
||||
<h2 className="text-xl mb-2">{collection}</h2>
|
||||
|
||||
<PatternButtons
|
||||
onClick={(id) => updateCodeWindow({ ...patterns[id], collection }, autoResetPatternOnChange)}
|
||||
started={context.started}
|
||||
patterns={patterns}
|
||||
activePattern={activePattern}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PatternPageWithPagination({ patterns, patternOnClick, context, paginationOnChange, initialPage }) {
|
||||
const [page, setPage] = useState(initialPage);
|
||||
const debouncedPageChange = useDebounce(() => {
|
||||
paginationOnChange(page);
|
||||
});
|
||||
|
||||
const onPageChange = (pageNum) => {
|
||||
setPage(pageNum);
|
||||
debouncedPageChange();
|
||||
};
|
||||
|
||||
const activePattern = useActivePattern();
|
||||
return (
|
||||
<div className="flex flex-grow flex-col h-full overflow-hidden justify-between">
|
||||
<div className="overflow-auto flex flex-col flex-grow bg-background p-2 rounded-md ">
|
||||
<PatternButtons
|
||||
onClick={(id) => patternOnClick(id)}
|
||||
started={context.started}
|
||||
patterns={patterns}
|
||||
activePattern={activePattern}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 py-2">
|
||||
<label htmlFor="pattern pagination">Page</label>
|
||||
<Pagination id="pattern pagination" currPage={page} onPageChange={onPageChange} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let featuredPageNum = 1;
|
||||
function FeaturedPatterns({ context }) {
|
||||
const examplePatterns = useExamplePatterns();
|
||||
const collections = examplePatterns.collections;
|
||||
const patterns = collections.get(patternFilterName.featured);
|
||||
return (
|
||||
<PatternPageWithPagination
|
||||
patterns={patterns}
|
||||
context={context}
|
||||
initialPage={featuredPageNum}
|
||||
patternOnClick={(id) => {
|
||||
updateCodeWindow(
|
||||
context,
|
||||
{ ...patterns[id], collection: patternFilterName.featured },
|
||||
autoResetPatternOnChange,
|
||||
);
|
||||
}}
|
||||
paginationOnChange={async (pageNum) => {
|
||||
await loadAndSetFeaturedPatterns(pageNum - 1);
|
||||
featuredPageNum = pageNum;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let latestPageNum = 1;
|
||||
function LatestPatterns({ context }) {
|
||||
const examplePatterns = useExamplePatterns();
|
||||
const collections = examplePatterns.collections;
|
||||
const patterns = collections.get(patternFilterName.public);
|
||||
return (
|
||||
<PatternPageWithPagination
|
||||
patterns={patterns}
|
||||
context={context}
|
||||
initialPage={latestPageNum}
|
||||
patternOnClick={(id) => {
|
||||
updateCodeWindow(context, { ...patterns[id], collection: patternFilterName.public }, autoResetPatternOnChange);
|
||||
}}
|
||||
paginationOnChange={async (pageNum) => {
|
||||
await loadAndSetPublicPatterns(pageNum - 1);
|
||||
latestPageNum = pageNum;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function PublicPatterns({ context }) {
|
||||
const { patternFilter } = useSettings();
|
||||
if (patternFilter === patternFilterName.featured) {
|
||||
return <FeaturedPatterns context={context} />;
|
||||
}
|
||||
return <LatestPatterns context={context} />;
|
||||
}
|
||||
|
||||
export function PatternsTab({ context }) {
|
||||
const { patternFilter } = useSettings();
|
||||
|
||||
return (
|
||||
<div className="px-4 w-full text-foreground space-y-2 flex flex-col overflow-hidden max-h-full h-full">
|
||||
<ButtonGroup
|
||||
value={patternFilter}
|
||||
onChange={(value) => settingsMap.setKey('patternFilter', value)}
|
||||
items={patternFilterName}
|
||||
></ButtonGroup>
|
||||
|
||||
{patternFilter === patternFilterName.user ? (
|
||||
<UserPatterns context={context} />
|
||||
) : (
|
||||
<PublicPatterns context={context} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import jsdocJson from '../../../../../doc.json';
|
||||
|
||||
import { Textbox } from '../textbox/Textbox';
|
||||
const availableFunctions = jsdocJson.docs
|
||||
.filter(({ name, description }) => name && !name.startsWith('_') && !!description)
|
||||
.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name));
|
||||
@ -29,12 +29,7 @@ export function Reference() {
|
||||
<div className="flex h-full w-full p-2 overflow-hidden">
|
||||
<div className="h-full flex flex-col gap-2 w-1/3 max-w-72 ">
|
||||
<div class="w-full flex">
|
||||
<input
|
||||
className="w-full p-1 bg-background rounded-md border-none"
|
||||
placeholder="Search"
|
||||
value={search}
|
||||
onInput={(event) => setSearch(event.target.value)}
|
||||
/>
|
||||
<Textbox className="w-full" placeholder="Search" value={search} onChange={setSearch} />
|
||||
</div>
|
||||
<div className="flex flex-col h-full overflow-y-auto gap-1.5 bg-background bg-opacity-50 rounded-md">
|
||||
{visibleFunctions.map((entry, i) => (
|
||||
|
||||
@ -5,6 +5,7 @@ import { useMemo, useRef, useState } from 'react';
|
||||
import { settingsMap, useSettings } from '../../../settings.mjs';
|
||||
import { ButtonGroup } from './Forms.jsx';
|
||||
import ImportSoundsButton from './ImportSoundsButton.jsx';
|
||||
import { Textbox } from '../textbox/Textbox.jsx';
|
||||
|
||||
const getSamples = (samples) =>
|
||||
Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1;
|
||||
@ -52,13 +53,8 @@ export function SoundsTab() {
|
||||
});
|
||||
|
||||
return (
|
||||
<div id="sounds-tab" className="px-4 flex flex-col w-full h-full text-foreground">
|
||||
<input
|
||||
className="w-full p-1 bg-background rounded-md my-2"
|
||||
placeholder="Search"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
<div id="sounds-tab" className="px-4 flex flex-col w-full h-full">
|
||||
<Textbox placeholder="Search" value={search} onChange={(v) => setSearch(v)} />
|
||||
|
||||
<div className="pb-2 flex shrink-0 flex-wrap">
|
||||
<ButtonGroup
|
||||
|
||||
@ -46,7 +46,8 @@ export function WelcomeTab({ context }) {
|
||||
<a href="https://github.com/tidalcycles/strudel" target="_blank">
|
||||
github
|
||||
</a>
|
||||
. Please consider to{' '}
|
||||
. You can also find <a href="https://github.com/felixroos/dough-samples/blob/main/README.md">licensing info</a>{' '}
|
||||
for the default sound banks there. Please consider to{' '}
|
||||
<a href="https://opencollective.com/tidalcycles" target="_blank">
|
||||
support this project
|
||||
</a>{' '}
|
||||
|
||||
11
website/src/repl/components/textbox/Textbox.jsx
Normal file
11
website/src/repl/components/textbox/Textbox.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
import cx from '@src/cx.mjs';
|
||||
|
||||
export function Textbox({ onChange, className, ...inputProps }) {
|
||||
return (
|
||||
<input
|
||||
className={cx('p-1 bg-background rounded-md my-2 border-foreground', className)}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
{...inputProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
30
website/src/repl/components/usedebounce.jsx
Normal file
30
website/src/repl/components/usedebounce.jsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useRef } from 'react';
|
||||
|
||||
function debounce(fn, wait) {
|
||||
let timer;
|
||||
return function (...args) {
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
timer = setTimeout(() => fn(...args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
export function useDebounce(callback) {
|
||||
const ref = useRef;
|
||||
useEffect(() => {
|
||||
ref.current = callback;
|
||||
}, [callback]);
|
||||
|
||||
const debouncedCallback = useMemo(() => {
|
||||
const func = () => {
|
||||
ref.current?.();
|
||||
};
|
||||
|
||||
return debounce(func, 1000);
|
||||
}, []);
|
||||
|
||||
return debouncedCallback;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { $featuredPatterns, $publicPatterns, collectionName } from '../user_pattern_utils.mjs';
|
||||
import { $featuredPatterns, $publicPatterns, patternFilterName } from '../user_pattern_utils.mjs';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useMemo } from 'react';
|
||||
import * as tunes from '../repl/tunes.mjs';
|
||||
@ -12,9 +12,9 @@ export const useExamplePatterns = () => {
|
||||
const publicPatterns = useStore($publicPatterns);
|
||||
const collections = useMemo(() => {
|
||||
const pats = new Map();
|
||||
pats.set(collectionName.featured, featuredPatterns);
|
||||
pats.set(collectionName.public, publicPatterns);
|
||||
// pats.set(collectionName.stock, stockPatterns);
|
||||
pats.set(patternFilterName.featured, featuredPatterns);
|
||||
pats.set(patternFilterName.public, publicPatterns);
|
||||
// pats.set(patternFilterName.stock, stockPatterns);
|
||||
return pats;
|
||||
}, [featuredPatterns, publicPatterns]);
|
||||
|
||||
|
||||
@ -8,16 +8,12 @@ import { confirmDialog, parseJSON, supabase } from './repl/util.mjs';
|
||||
export let $publicPatterns = atom([]);
|
||||
export let $featuredPatterns = atom([]);
|
||||
|
||||
export const collectionName = {
|
||||
user: 'user',
|
||||
public: 'Last Creations',
|
||||
stock: 'Stock Examples',
|
||||
featured: 'Featured',
|
||||
};
|
||||
|
||||
const patternQueryLimit = 20;
|
||||
export const patternFilterName = {
|
||||
community: 'community',
|
||||
public: 'latest',
|
||||
featured: 'featured',
|
||||
user: 'user',
|
||||
// stock: 'stock examples',
|
||||
};
|
||||
|
||||
const sessionAtom = (name, initial = undefined) => {
|
||||
@ -36,7 +32,7 @@ const sessionAtom = (name, initial = undefined) => {
|
||||
export let $viewingPatternData = sessionAtom('viewingPatternData', {
|
||||
id: '',
|
||||
code: '',
|
||||
collection: collectionName.user,
|
||||
collection: patternFilterName.user,
|
||||
created_at: Date.now(),
|
||||
});
|
||||
|
||||
@ -51,25 +47,50 @@ export const setViewingPatternData = (data) => {
|
||||
$viewingPatternData.set(JSON.stringify(data));
|
||||
};
|
||||
|
||||
export function loadPublicPatterns() {
|
||||
return supabase.from('code_v1').select().eq('public', true).limit(20).order('id', { ascending: false });
|
||||
function parsePageNum(page) {
|
||||
return isNaN(page) ? 0 : page;
|
||||
}
|
||||
export function loadPublicPatterns(page) {
|
||||
page = parsePageNum(page);
|
||||
const offset = page * patternQueryLimit;
|
||||
return supabase
|
||||
.from('code_v1')
|
||||
.select()
|
||||
.eq('public', true)
|
||||
.range(offset, offset + patternQueryLimit)
|
||||
.order('id', { ascending: false });
|
||||
}
|
||||
|
||||
export function loadFeaturedPatterns() {
|
||||
return supabase.from('code_v1').select().eq('featured', true).limit(20).order('id', { ascending: false });
|
||||
export function loadFeaturedPatterns(page = 0) {
|
||||
page = parsePageNum(page);
|
||||
const offset = page * patternQueryLimit;
|
||||
return supabase
|
||||
.from('code_v1')
|
||||
.select()
|
||||
.eq('featured', true)
|
||||
.range(offset, offset + patternQueryLimit)
|
||||
.order('id', { ascending: false });
|
||||
}
|
||||
|
||||
export async function loadAndSetPublicPatterns(page) {
|
||||
const p = await loadPublicPatterns(page);
|
||||
const data = p?.data;
|
||||
const pats = {};
|
||||
data?.forEach((data, key) => (pats[data.id ?? key] = data));
|
||||
$publicPatterns.set(pats);
|
||||
}
|
||||
export async function loadAndSetFeaturedPatterns(page) {
|
||||
const p = await loadFeaturedPatterns(page);
|
||||
const data = p?.data;
|
||||
const pats = {};
|
||||
data?.forEach((data, key) => (pats[data.id ?? key] = data));
|
||||
$featuredPatterns.set(pats);
|
||||
}
|
||||
|
||||
export async function loadDBPatterns() {
|
||||
try {
|
||||
const { data: publicPatterns } = await loadPublicPatterns();
|
||||
const { data: featuredPatterns } = await loadFeaturedPatterns();
|
||||
const featured = {};
|
||||
const pub = {};
|
||||
|
||||
publicPatterns?.forEach((data, key) => (pub[data.id ?? key] = data));
|
||||
featuredPatterns?.forEach((data, key) => (featured[data.id ?? key] = data));
|
||||
$publicPatterns.set(pub);
|
||||
$featuredPatterns.set(featured);
|
||||
await loadAndSetPublicPatterns();
|
||||
await loadAndSetFeaturedPatterns();
|
||||
} catch (err) {
|
||||
console.error('error loading patterns', err);
|
||||
}
|
||||
@ -92,7 +113,7 @@ export const setLatestCode = (code) => settingsMap.setKey('latestCode', code);
|
||||
|
||||
const defaultCode = '';
|
||||
export const userPattern = {
|
||||
collection: collectionName.user,
|
||||
collection: patternFilterName.user,
|
||||
getAll() {
|
||||
const patterns = parseJSON(settingsMap.get().userPatterns);
|
||||
return patterns ?? {};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user