From 1e2c26123bcfaa16fe3dfeda824f316e15ef1208 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 3 Jan 2024 00:35:47 -0500 Subject: [PATCH 01/31] working --- website/src/repl/Header.jsx | 2 +- website/src/repl/Repl.jsx | 12 +++-- website/src/repl/panel/PatternsTab.jsx | 69 ++++++++++++-------------- 3 files changed, 41 insertions(+), 42 deletions(-) diff --git a/website/src/repl/Header.jsx b/website/src/repl/Header.jsx index 774d0142..f806d5d3 100644 --- a/website/src/repl/Header.jsx +++ b/website/src/repl/Header.jsx @@ -85,7 +85,7 @@ export function Header({ context }) { )} )} - {!isExample && ( - )} diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 61ad08d5..8872bdc7 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -152,14 +152,13 @@ export function getUserPattern(key) { return userPatterns[key]; } -export function renameActivePattern() { - let activePattern = getActivePattern(); +export function renamePattern(pattern) { let userPatterns = getUserPatterns(); - if (!userPatterns[activePattern]) { + if (!userPatterns[pattern]) { alert('Cannot rename examples'); return; } - const newName = prompt('Enter new name', activePattern); + const newName = prompt('Enter new name', pattern); if (newName === null) { // canceled return; @@ -168,10 +167,10 @@ export function renameActivePattern() { alert('Name already taken!'); return; } - userPatterns[newName] = userPatterns[activePattern]; // copy code - delete userPatterns[activePattern]; + userPatterns[newName] = userPatterns[pattern]; // copy code + delete userPatterns[pattern]; setUserPatterns({ ...userPatterns }); - setActivePattern(newName); + setViewingPattern(newName); } export function updateUserCode(code) { @@ -196,35 +195,34 @@ export function updateUserCode(code) { setUserPatterns({ ...userPatterns, [activePattern]: { code } }); } -export function deleteActivePattern() { - let activePattern = getActivePattern(); - if (!activePattern) { +export function deletePattern(pattern) { + + if (!pattern) { console.warn('cannot delete: no pattern selected'); return; } const userPatterns = getUserPatterns(); - if (!userPatterns[activePattern]) { + if (!userPatterns[pattern]) { alert('Cannot delete examples'); return; } - if (!confirm(`Really delete the selected pattern "${activePattern}"?`)) { + if (!confirm(`Really delete the selected pattern "${pattern}"?`)) { return; } - setUserPatterns(Object.fromEntries(Object.entries(userPatterns).filter(([key]) => key !== activePattern))); - setActivePattern(''); + setUserPatterns(Object.fromEntries(Object.entries(userPatterns).filter(([key]) => key !== pattern))); + setViewingPattern(''); } -export function duplicateActivePattern() { - let activePattern = getActivePattern(); +export function duplicatePattern(pattern) { let latestCode = getSetting('latestCode'); - if (!activePattern) { + if (!pattern) { console.warn('cannot duplicate: no pattern selected'); return; } const userPatterns = getUserPatterns(); - activePattern = getNextCloneName(activePattern); - setUserPatterns({ ...userPatterns, [activePattern]: { code: latestCode } }); - setActivePattern(activePattern); + const newPattern = getNextCloneName(pattern); + setUserPatterns({ ...userPatterns, [newPattern]: { code: latestCode } }); + setViewingPattern(newPattern); } export async function importPatterns(fileList) { From 4e0a60db003675fd68e9fb1e8ccb25bf47663030 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 6 Jan 2024 22:10:14 -0500 Subject: [PATCH 05/31] example code logic --- website/src/repl/Repl.jsx | 24 +++++++--- website/src/repl/panel/PatternsTab.jsx | 19 +++++--- website/src/repl/tunes.mjs | 62 ++++++++++++++++++++++++++ website/src/repl/util.mjs | 1 + website/src/settings.mjs | 29 +++--------- 5 files changed, 98 insertions(+), 37 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 62839ecd..75ef0278 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -20,12 +20,14 @@ import { useSettings, getViewingPattern, setViewingPattern, + getNextCloneName, } from '../settings.mjs'; import { Header } from './Header'; import Loader from './Loader'; import { Panel } from './panel/Panel'; import { useStore } from '@nanostores/react'; import { prebake } from './prebake.mjs'; +import * as tunes from './tunes.mjs'; import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs'; import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; import './Repl.css'; @@ -44,7 +46,6 @@ if (typeof window !== 'undefined') { clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width); isIframe = window.location !== window.parent.location; } - export function Repl({ embedded = false }) { const isEmbedded = embedded || isIframe; const { panelPosition, isZen } = useSettings(); @@ -74,13 +75,23 @@ export function Repl({ embedded = false }) { setReplState({ ...state }); }, afterEval: ({ code }) => { - updateUserCode(code); - // setPending(false); setLatestCode(code); - const viewingPatternID = getViewingPattern(); + let pattern = getViewingPattern(); window.location.hash = '#' + code2hash(code); - if (viewingPatternID != null) { - setActivePattern(viewingPatternID); + const isExamplePattern = !!tunes[pattern]; + + if (isExamplePattern) { + const codeHasChanged = code !== tunes[pattern]; + if (codeHasChanged) { + // fork example + pattern = getNextCloneName(pattern); + setViewingPattern(pattern); + updateUserCode(pattern, code); + } + setActivePattern(pattern); + } else { + setActivePattern(pattern); + updateUserCode(pattern, code); } }, bgFill: false, @@ -149,6 +160,7 @@ export function Repl({ embedded = false }) { // payload = {reset?: boolean, code?: string, evaluate?: boolean, patternID?: string } const handleUpdate = async (payload) => { const { reset = false, code = null, evaluate = true, patternID = null } = payload; + if (reset) { clearCanvas(); resetLoadedSounds(); diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index 59c1f421..a5ded1f0 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -24,8 +24,8 @@ function PatternButton({ showOutline, onClick, label, showHiglight }) { @@ -54,15 +54,15 @@ export function PatternsTab({ context }) { const { userPatterns } = useSettings(); const activePattern = useActivePattern(); const viewingPattern = useViewingPattern(); - const isExample = useMemo(() => activePattern && !!tunes[activePattern], [activePattern]); + // const isExample = useMemo(() => activePattern && !!tunes[activePattern], [activePattern]); const onPatternClick = (key, data) => { - const { code } = data; - context.handleUpdate({ patternID: key, code, evaluate: false }); + // display selected pattern code in the window + context.handleUpdate({ patternID: key, code: data.code, evaluate: false }); }; const examplePatterns = {}; Object.entries(tunes).forEach(([key, code]) => (examplePatterns[key] = { code })); - + const isExample = examplePatterns[viewingPattern] != null; return (
@@ -124,7 +124,12 @@ export function PatternsTab({ context }) {

Examples

- +
); diff --git a/website/src/repl/tunes.mjs b/website/src/repl/tunes.mjs index 5f68ee93..ef847774 100644 --- a/website/src/repl/tunes.mjs +++ b/website/src/repl/tunes.mjs @@ -5,6 +5,9 @@ This program is free software: you can redistribute it and/or modify it under th */ export const swimming = `// Koji Kondo - Swimming (Super Mario World) + +setCps(1) + stack( seq( "~", @@ -69,6 +72,8 @@ stack( export const giantSteps = `// John Coltrane - Giant Steps +setCps(1) + let melody = note( "[F#5 D5] [B4 G4] Bb4 [B4 A4]", "[D5 Bb4] [G4 Eb4] F#4 [G4 F4]", @@ -99,6 +104,9 @@ stack( .pianoroll({fold:1})`; export const zeldasRescue = `// Koji Kondo - Princess Zelda's Rescue + +setCps(1) + stack( // melody \`[B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3] @@ -128,6 +136,9 @@ export const caverave = `// "Caverave" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos + +setCps(1) + const keys = x => x.s('sawtooth').cutoff(1200).gain(.5) .attack(0).decay(.16).sustain(.3).release(.1); @@ -174,6 +185,8 @@ export const sampleDrums = `samples({ hh: 'hh/000_hh3closedhh.wav' }, 'https://loophole-letters.vercel.app/samples/tidal/') +setCps(1) + stack( "".color('#F5A623'), "hh*4".color('#673AB7'), @@ -183,6 +196,9 @@ stack( `; export const barryHarris = `// adapted from a Barry Harris excercise + +setCps(1) + "0,2,[7 6]" .add("<0 1 2 3 4 5 7 8>") .scale('C bebop major') @@ -196,6 +212,8 @@ export const blippyRhodes = `// "Blippy Rhodes" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + samples({ bd: 'samples/tidal/bd/BT0A0D0.wav', sn: 'samples/tidal/sn/ST0T0S3.wav', @@ -239,6 +257,8 @@ export const wavyKalimba = `// "Wavy kalimba" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + samples({ 'kalimba': { c5:'https://freesound.org/data/previews/536/536549_11935698-lq.mp3' } }) @@ -269,6 +289,8 @@ export const festivalOfFingers = `// "Festival of fingers" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + const chords = ""; stack( chord(chords).dict('lefthand').voicing().struct("x(3,8,-1)") @@ -292,6 +314,8 @@ export const undergroundPlumber = `// "Underground plumber" // @by Felix Roos // @details inspired by Friendship - Let's not talk about it (1979) :) +setCps(1) + samples({ bd: 'bd/BT0A0D0.wav', sn: 'sn/ST0T0S3.wav', hh: 'hh/000_hh3closedhh.wav', cp: 'cp/HANDCLP0.wav', }, 'https://loophole-letters.vercel.app/samples/tidal/') @@ -319,6 +343,8 @@ export const bridgeIsOver = `// "Bridge is over" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos, bassline by BDP - The Bridge Is Over +setCps(1) + samples({mad:'https://freesound.org/data/previews/22/22274_109943-lq.mp3'}) stack( stack( @@ -339,6 +365,8 @@ export const goodTimes = `// "Good times" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + const scale = cat('C3 dorian','Bb2 major').slow(4); stack( n("2*4".add(12)).off(1/8, add(2)) @@ -361,6 +389,8 @@ export const echoPiano = `// "Echo piano" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + n("<0 2 [4 6](3,4,2) 3*2>").color('salmon') .off(1/4, x=>x.add(2).color('green')) .off(1/2, x=>x.add(6).color('steelblue')) @@ -371,6 +401,9 @@ n("<0 2 [4 6](3,4,2) 3*2>").color('salmon') .pianoroll()`; export const sml1 = `// Hirokazu Tanaka - World 1-1 + +setCps(1) + stack( // melody note(\`< @@ -409,6 +442,8 @@ export const randomBells = `// "Random bells" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + samples({ bell: { c6: 'https://freesound.org/data/previews/411/411089_5121236-lq.mp3' }, bass: { d2: 'https://freesound.org/data/previews/608/608286_13074022-lq.mp3' } @@ -432,6 +467,8 @@ export const waa2 = `// "Waa2" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + note( "a4 [a3 c3] a3 c3" .sub("<7 12 5 12>".slow(2)) @@ -450,6 +487,8 @@ export const hyperpop = `// "Hyperpop" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + const lfo = cosine.slow(15); const lfo2 = sine.slow(16); const filter1 = x=>x.cutoff(lfo2.range(300,3000)); @@ -505,6 +544,8 @@ export const festivalOfFingers3 = `// "Festival of fingers 3" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + n("[-7*3],0,2,6,[8 7]") .echoWith( 4, // echo 4 times @@ -528,6 +569,8 @@ export const meltingsubmarine = `// "Melting submarine" // @by Felix Roos await samples('github:tidalcycles/Dirt-Samples/master/') +setCps(1) + stack( s("bd:5,[~ ],hh27(3,4,1)") // drums .speed(perlin.range(.7,.9)) // random sample speed variation @@ -609,6 +652,7 @@ bd: ['bd/BT0AADA.wav','bd/BT0AAD0.wav','bd/BT0A0DA.wav','bd/BT0A0D3.wav','bd/BT0 sd: ['sd/rytm-01-classic.wav','sd/rytm-00-hard.wav'], hh: ['hh27/000_hh27closedhh.wav','hh/000_hh3closedhh.wav'], }, 'github:tidalcycles/Dirt-Samples/master/'); +setCps(1) note("<8(3,8) <7 7*2> [4 5@3] 8>".sub(1) // sub 1 -> 1-indexed .layer( @@ -631,6 +675,7 @@ export const chop = `// "Chop" // @by Felix Roos samples({ p: 'https://cdn.freesound.org/previews/648/648433_11943129-lq.mp3' }) +setCps(1) s("p") .loopAt(32) @@ -656,6 +701,8 @@ export const orbit = `// "Orbit" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + stack( s("bd ") .delay(.5) @@ -673,6 +720,8 @@ export const belldub = `// "Belldub" // @by Felix Roos samples({ bell: {b4:'https://cdn.freesound.org/previews/339/339809_5121236-lq.mp3'}}) +setCps(1) + // "Hand Bells, B, Single.wav" by InspectorJ (www.jshaw.co.uk) of Freesound.org stack( // bass @@ -712,6 +761,7 @@ export const dinofunk = `// "Dinofunk" samples({bass:'https://cdn.freesound.org/previews/614/614637_2434927-hq.mp3', dino:{b4:'https://cdn.freesound.org/previews/316/316403_5123851-hq.mp3'}}) setVoicingRange('lefthand', ['c3','a4']) +setCps(1) stack( s('bass').loopAt(8).clip(1), @@ -736,6 +786,8 @@ export const sampleDemo = `// "Sample demo" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + stack( // percussion s("[woodblock:1 woodblock:2*2] snare_rim:0,gong/8,brakedrum:1(3,8),~@3 cowbell:3") @@ -754,6 +806,8 @@ export const holyflute = `// "Holy flute" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + "c3 eb3(3,8) c4/2 g3*2" .superimpose( x=>x.slow(2).add(12), @@ -795,6 +849,8 @@ export const amensister = `// "Amensister" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + await samples('github:tidalcycles/Dirt-Samples/master') stack( @@ -833,6 +889,8 @@ export const juxUndTollerei = `// "Jux und tollerei" // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + note("c3 eb3 g3 bb3").palindrome() .s('sawtooth') .jux(x=>x.rev().color('green').s('sawtooth')) @@ -848,6 +906,8 @@ export const csoundDemo = `// "CSound demo" // @license with CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @by Felix Roos +setCps(1) + await loadCsound\` instr CoolSynth iduration = p3 @@ -885,6 +945,7 @@ export const loungeSponge = `// "Lounge sponge" // @by Felix Roos, livecode.orc by Steven Yi await loadOrc('github:kunstmusik/csound-live-code/master/livecode.orc') +setCps(1) stack( chord("/2").dict('lefthand').voicing() @@ -905,6 +966,7 @@ export const arpoon = `// "Arpoon" // @by Felix Roos await samples('github:tidalcycles/Dirt-Samples/master') +setCps(1) n("[0,3] 2 [1,3] 2".fast(3).lastOf(4, fast(2))).clip(2) .offset("<<1 2> 2 1 1>") diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index 2b93b619..3f7307a1 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -110,3 +110,4 @@ export async function shareCode(codeToShare) { logger(message); } } + diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 8872bdc7..0d38d6b8 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -1,7 +1,6 @@ import { persistentMap, persistentAtom } from '@nanostores/persistent'; import { useStore } from '@nanostores/react'; import { register } from '@strudel.cycles/core'; -import * as tunes from './repl/tunes.mjs'; import { defaultAudioDeviceName } from './repl/panel/AudioDeviceSelector'; import { logger } from '@strudel.cycles/core'; @@ -126,11 +125,10 @@ export function newUserPattern() { const date = new Date().toISOString().split('T')[0]; const todays = Object.entries(userPatterns).filter(([name]) => name.startsWith(date)); const num = String(todays.length + 1).padStart(3, '0'); - const defaultNewPattern = 's("hh")'; const name = date + '_' + num; - addUserPattern(name, { code: defaultNewPattern }); - setActivePattern(name); - return name; + const code = 's("hh")'; + setUserPatterns({ ...userPatterns, [name]: { code } }); + setViewingPattern(name); } export function clearUserPatterns() { @@ -173,26 +171,9 @@ export function renamePattern(pattern) { setViewingPattern(newName); } -export function updateUserCode(code) { +export function updateUserCode(pattern, code ) { const userPatterns = getUserPatterns(); - let activePattern = getActivePattern(); - // check if code is that of an example tune - const [example] = Object.entries(tunes).find(([_, tune]) => tune === code) || []; - if (example && (!activePattern || activePattern === example)) { - // select example - setActivePattern(example); - return; - } - if (!activePattern) { - // create new user pattern - activePattern = newUserPattern(); - setActivePattern(activePattern); - } else if (!!tunes[activePattern] && code !== tunes[activePattern]) { - // fork example - activePattern = getNextCloneName(activePattern); - setActivePattern(activePattern); - } - setUserPatterns({ ...userPatterns, [activePattern]: { code } }); + setUserPatterns({ ...userPatterns, [pattern]: { code } }); } export function deletePattern(pattern) { From 0355c358b8d4cb126e2c48061b033357ea1c6eb5 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 6 Jan 2024 23:49:37 -0500 Subject: [PATCH 06/31] fixed create and duplicate behavior --- website/src/repl/Repl.jsx | 8 ++++---- website/src/repl/panel/PatternsTab.jsx | 27 ++++++++++++++++---------- website/src/settings.mjs | 13 +++++-------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 75ef0278..384e78d3 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -157,9 +157,9 @@ export function Repl({ embedded = false }) { const handleTogglePlay = async () => editorRef.current?.toggle(); - // payload = {reset?: boolean, code?: string, evaluate?: boolean, patternID?: string } + // payload = {reset?: boolean, code?: string, evaluate?: boolean, pattern?: string } const handleUpdate = async (payload) => { - const { reset = false, code = null, evaluate = true, patternID = null } = payload; + const { reset = false, code = null, evaluate = true, pattern = null } = payload; if (reset) { clearCanvas(); @@ -167,9 +167,9 @@ export function Repl({ embedded = false }) { editorRef.current.repl.setCps(1); await prebake(); // declare default samples } - if (code != null) { + if (code != null && pattern != null) { editorRef.current.setCode(code); - setViewingPattern(patternID); + setViewingPattern(pattern); } if (evaluate) { editorRef.current.evaluate(); diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index a5ded1f0..b5439220 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -1,13 +1,13 @@ import { DocumentDuplicateIcon, PencilSquareIcon, TrashIcon } from '@heroicons/react/20/solid'; -import { useMemo } from 'react'; + import { clearUserPatterns, deletePattern, - duplicatePattern, + createDuplicatePattern, exportPatterns, - getUserPattern, + addUserPattern, importPatterns, - newUserPattern, + createNewUserPattern, renamePattern, useActivePattern, useViewingPattern, @@ -55,9 +55,14 @@ export function PatternsTab({ context }) { const activePattern = useActivePattern(); const viewingPattern = useViewingPattern(); // const isExample = useMemo(() => activePattern && !!tunes[activePattern], [activePattern]); - const onPatternClick = (key, data) => { + const onPatternClick = (pattern, data) => { // display selected pattern code in the window - context.handleUpdate({ patternID: key, code: data.code, evaluate: false }); + context.handleUpdate({ pattern, code: data.code, evaluate: false }); + }; + + const addPattern = ({ pattern, code }) => { + addUserPattern(pattern, { code }); + context.handleUpdate({ code, pattern, evaluate: false }); }; const examplePatterns = {}; @@ -76,7 +81,11 @@ export function PatternsTab({ context }) { {/* */} )} - {!isExample && ( @@ -97,9 +106,7 @@ export function PatternsTab({ context }) { {!isExample && ( - )} diff --git a/website/src/settings.mjs b/website/src/settings.mjs index da9d4b12..2333d4a6 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -27,7 +27,6 @@ export const defaultSettings = { export const settingsMap = persistentMap('strudel-settings', defaultSettings); - //pattern that the use is currently viewing in the window const $viewingPattern = persistentAtom('viewingPattern', '', { listen: false }); export function setViewingPattern(key) { @@ -127,7 +126,7 @@ export function createNewUserPattern() { const num = String(todays.length + 1).padStart(3, '0'); const pattern = date + '_' + num; const code = 's("hh")'; - return {pattern, code} + return { pattern, code }; } export function clearUserPatterns() { @@ -170,13 +169,12 @@ export function renamePattern(pattern) { setViewingPattern(newName); } -export function updateUserCode(pattern, code ) { +export function updateUserCode(pattern, code) { const userPatterns = getUserPatterns(); setUserPatterns({ ...userPatterns, [pattern]: { code } }); } export function deletePattern(pattern) { - if (!pattern) { console.warn('cannot delete: no pattern selected'); return; @@ -189,8 +187,35 @@ export function deletePattern(pattern) { if (!confirm(`Really delete the selected pattern "${pattern}"?`)) { return; } - setUserPatterns(Object.fromEntries(Object.entries(userPatterns).filter(([key]) => key !== pattern))); - setViewingPattern(''); + // const updatedPatterns = Object.fromEntries(Object.entries(userPatterns).filter(([key]) => key !== pattern)); + let patternsArray = Object.entries(userPatterns).sort((a, b) => a[0].localeCompare(b[0])); + const deleteIndex = patternsArray.findIndex(([key]) => key === pattern); + patternsArray.splice(deleteIndex, 1); + const updatedPatterns = Object.fromEntries(patternsArray); + + setUserPatterns(updatedPatterns); + + //create new pattern if no other patterns + if (!patternsArray.length) { + return createNewUserPattern(); + } + // // or default to active pattern + // const activePatternID = getActivePattern(); + // const activePatternData = updatedPatterns[activePatternID]; + // if (activePatternData?.code != null) { + // return { pattern: activePatternID, code: activePatternData.code }; + // } + // or find pattern at next index + + const next = patternsArray[deleteIndex]; + if (next != null) { + const [pat, data] = next; + return { pattern: pat, code: data.code }; + } + // or find pattern at previous index + const previous = patternsArray[deleteIndex - 1]; + const [pat, data] = previous; + return { patttern: pat, code: data.code }; } export function createDuplicatePattern(pattern) { @@ -199,8 +224,8 @@ export function createDuplicatePattern(pattern) { console.warn('cannot duplicate: no pattern selected'); return; } - const newPattern = getNextCloneName(pattern); - return {pattern: newPattern, code: latestCode} + const newPattern = getNextCloneName(pattern); + return { pattern: newPattern, code: latestCode }; } export async function importPatterns(fileList) { From 1ff7548b52d8a6365039254f6328ce54897dc25a Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 7 Jan 2024 22:48:07 -0500 Subject: [PATCH 08/31] refactoring --- website/src/repl/Repl.jsx | 61 ++++--- website/src/repl/panel/PatternsTab.jsx | 80 +++++---- website/src/settings.mjs | 218 ++++++++++++------------- 3 files changed, 189 insertions(+), 170 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 384e78d3..1c45c47f 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -16,18 +16,19 @@ import { setActivePattern, setLatestCode, settingsMap, - updateUserCode, useSettings, getViewingPattern, setViewingPattern, - getNextCloneName, + createPatternID, + userPattern, + examplePattern, + getNextCloneID, } from '../settings.mjs'; import { Header } from './Header'; import Loader from './Loader'; import { Panel } from './panel/Panel'; import { useStore } from '@nanostores/react'; import { prebake } from './prebake.mjs'; -import * as tunes from './tunes.mjs'; import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs'; import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; import './Repl.css'; @@ -76,22 +77,26 @@ export function Repl({ embedded = false }) { }, afterEval: ({ code }) => { setLatestCode(code); - let pattern = getViewingPattern(); + const data = { code }; + let id = getViewingPattern(); window.location.hash = '#' + code2hash(code); - const isExamplePattern = !!tunes[pattern]; + const examplePatternData = examplePattern.getPatternData(id); + const isExamplePattern = examplePatternData != null; if (isExamplePattern) { - const codeHasChanged = code !== tunes[pattern]; + const codeHasChanged = code !== examplePatternData.code; if (codeHasChanged) { // fork example - pattern = getNextCloneName(pattern); - setViewingPattern(pattern); - updateUserCode(pattern, code); + id = getNextCloneID(id); + setViewingPattern(id); + userPattern.update(id, data); } - setActivePattern(pattern); + setActivePattern(id); } else { - setActivePattern(pattern); - updateUserCode(pattern, code); + id = id == null ? createPatternID() : id; + setActivePattern(id); + setViewingPattern(id); + userPattern.update(id, data); } }, bgFill: false, @@ -159,21 +164,25 @@ export function Repl({ embedded = false }) { // payload = {reset?: boolean, code?: string, evaluate?: boolean, pattern?: string } const handleUpdate = async (payload) => { - const { reset = false, code = null, evaluate = true, pattern = null } = payload; + const { id, code } = payload; + setViewingPattern(id); + editorRef.current.setCode(code); - if (reset) { - clearCanvas(); - resetLoadedSounds(); - editorRef.current.repl.setCps(1); - await prebake(); // declare default samples - } - if (code != null && pattern != null) { - editorRef.current.setCode(code); - setViewingPattern(pattern); - } - if (evaluate) { - editorRef.current.evaluate(); - } + // const { reset = false, code = null, evaluate = true, pattern = null } = payload; + + // if (reset) { + // clearCanvas(); + // resetLoadedSounds(); + // editorRef.current.repl.setCps(1); + // await prebake(); // declare default samples + // } + // if (code != null && pattern != null) { + // setViewingPattern(pattern); + // editorRef.current.setCode(code); + // } + // if (evaluate) { + // editorRef.current.evaluate(); + // } }; const handleShuffle = async () => { // window.postMessage('strudel-shuffle'); diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index 70a581ad..d8ce363e 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -1,19 +1,15 @@ import { DocumentDuplicateIcon, PencilSquareIcon, TrashIcon } from '@heroicons/react/20/solid'; import { - clearUserPatterns, - deletePattern, - createDuplicatePattern, exportPatterns, - addUserPattern, importPatterns, - createNewUserPattern, - renamePattern, useActivePattern, useViewingPattern, useSettings, + userPattern, + examplePattern, } from '../../settings.mjs'; -import * as tunes from '../tunes.mjs'; +import { useMemo } from 'react'; function classNames(...classes) { return classes.filter(Boolean).join(' '); @@ -34,16 +30,16 @@ function PatternButton({ showOutline, onClick, label, showHiglight }) { ); } -function PatternButtons({ patterns, activePattern, onClick, viewingPattern }) { +function PatternButtons({ patterns, activePattern, onClick, viewingPattern, isExample = false }) { return (
- {Object.entries(patterns).map(([key, data]) => ( + {Object.entries(patterns).map(([id]) => ( onClick(key, data)} + key={id} + label={id} + showHiglight={id === viewingPattern} + showOutline={id === activePattern} + onClick={() => onClick(id, isExample)} /> ))}
@@ -52,20 +48,20 @@ function PatternButtons({ patterns, activePattern, onClick, viewingPattern }) { export function PatternsTab({ context }) { const { userPatterns } = useSettings(); + const examplePatterns = useMemo(() => examplePattern.getAll(), []); const activePattern = useActivePattern(); const viewingPattern = useViewingPattern(); - const onPatternClick = (pattern, data) => { + + const updateCodeWindow = (id, code) => { + context.handleUpdate({ code, id, evaluate: false }); + }; + const onPatternBtnClick = (id, isExample) => { + const code = isExample ? examplePatterns[id].code : userPatterns[id].code; + // display selected pattern code in the window - context.handleUpdate({ pattern, code: data.code, evaluate: false }); + updateCodeWindow(id, code); }; - const addPattern = ({ pattern, code }) => { - addUserPattern(pattern, { code }); - context.handleUpdate({ code, pattern, evaluate: false }); - }; - - const examplePatterns = {}; - Object.entries(tunes).forEach(([key, code]) => (examplePatterns[key] = { code })); const isExample = examplePatterns[viewingPattern] != null; return (
@@ -75,14 +71,27 @@ export function PatternsTab({ context }) {

{viewingPattern}

{!isExample && ( - )}
)} { - addPattern(createNewUserPattern()); + const { id, data } = userPattern.create(); + updateCodeWindow(id, data.code); }} > new -

Examples

pat.code === code); - setActivePattern(match?.[0] || ''); + const id = match?.[0] || ''; + setActivePattern(id); + setViewingPattern(id); } export function useSettings() { @@ -105,128 +109,116 @@ function getSetting(key) { } export function setUserPatterns(obj) { - settingsMap.setKey('userPatterns', JSON.stringify(obj)); + return settingsMap.setKey('userPatterns', JSON.stringify(obj)); } -export function addUserPattern(name, config) { - if (typeof config !== 'object') { - throw new Error('addUserPattern expected object as second param'); - } - if (!config.code) { - throw new Error('addUserPattern expected code as property of second param'); - } - const userPatterns = getUserPatterns(); - setUserPatterns({ ...userPatterns, [name]: config }); -} - -export function createNewUserPattern() { +export const createPatternID = () => { const userPatterns = getUserPatterns(); const date = new Date().toISOString().split('T')[0]; const todays = Object.entries(userPatterns).filter(([name]) => name.startsWith(date)); const num = String(todays.length + 1).padStart(3, '0'); - const pattern = date + '_' + num; - const code = 's("hh")'; - return { pattern, code }; -} + const id = date + '_' + num; + return id; +}; -export function clearUserPatterns() { - if (!confirm(`This will delete all your patterns. Are you really sure?`)) { - return; - } - setUserPatterns({}); -} - -export function getNextCloneName(key) { - const userPatterns = getUserPatterns(); - const clones = Object.entries(userPatterns).filter(([name]) => name.startsWith(key)); +export const getNextCloneID = (id) => { + const userPatterns = this.getAll(); + const clones = Object.entries(userPatterns).filter(([patID]) => patID.startsWith(id)); const num = String(clones.length + 1).padStart(3, '0'); - return key + '_' + num; -} + const newID = id + '_' + num; + return newID; +}; -export function getUserPattern(key) { - const userPatterns = getUserPatterns(); - return userPatterns[key]; -} +export const examplePattern = { + getAll() { + const examplePatterns = {}; + Object.entries(tunes).forEach(([key, code]) => (examplePatterns[key] = { code })); + return examplePatterns; + }, + getPatternData(id) { + const userPatterns = this.getAll(); + return userPatterns[id]; + }, +}; -export function renamePattern(pattern) { - let userPatterns = getUserPatterns(); - if (!userPatterns[pattern]) { - alert('Cannot rename examples'); - return; - } - const newName = prompt('Enter new name', pattern); - if (newName === null) { - // canceled - return; - } - if (userPatterns[newName]) { - alert('Name already taken!'); - return; - } - userPatterns[newName] = userPatterns[pattern]; // copy code - delete userPatterns[pattern]; - setUserPatterns({ ...userPatterns }); - setViewingPattern(newName); -} +export const userPattern = { + getAll() { + return JSON.parse(settingsMap.get().userPatterns); + }, + getPatternData(id) { + const userPatterns = this.getAll(); + return userPatterns[id]; + }, + exists(id) { + const userPatterns = this.getAll(); + return userPatterns[id] != null; + }, + create() { + const newID = createPatternID(); + const code = defaultCode; + const data = { code }; + this.update(newID, data); + return { id: newID, data }; + }, + update(id, data) { + const userPatterns = this.getAll(); + setUserPatterns({ ...userPatterns, [id]: data }); + }, + duplicate(id, data) { + const newID = getNextCloneID(id); + this.update(newID, data); + return { id: newID, data }; + }, + clearAll() { + if (!confirm(`This will delete all your patterns. Are you really sure?`)) { + return; + } + const viewingPattern = getViewingPattern(); + const examplePatternData = examplePattern.getPatternData(viewingPattern); + setUserPatterns({}); + if (examplePatternData != null) { + return { id: viewingPattern, data: examplePatternData }; + } + setViewingPattern(null); + setActivePattern(null); + return { id: null, data: { code: '' } }; + }, + delete(id) { + const userPatterns = this.getAll(); + const updatedPatterns = Object.fromEntries(Object.entries(userPatterns).filter(([key]) => key !== id)); + if (getActivePattern() === id) { + setActivePattern(null); + } + setUserPatterns(updatedPatterns); + const viewingPattern = getViewingPattern(); + if (viewingPattern === id) { + return this.create(); + } + return { id: viewingPattern, data: updatedPatterns[id] }; + }, -export function updateUserCode(pattern, code) { - const userPatterns = getUserPatterns(); - setUserPatterns({ ...userPatterns, [pattern]: { code } }); -} + rename(id) { + const userPatterns = this.getAll(); + const newID = prompt('Enter new name', id); + if (newID === null) { + // canceled + return; + } + if (userPatterns[newID]) { + alert('Name already taken!'); + return; + } + const data = userPatterns[id]; + userPatterns[newID] = data; // copy code + delete userPatterns[id]; -export function deletePattern(pattern) { - if (!pattern) { - console.warn('cannot delete: no pattern selected'); - return; - } - const userPatterns = getUserPatterns(); - if (!userPatterns[pattern]) { - alert('Cannot delete examples'); - return; - } - if (!confirm(`Really delete the selected pattern "${pattern}"?`)) { - return; - } - // const updatedPatterns = Object.fromEntries(Object.entries(userPatterns).filter(([key]) => key !== pattern)); - let patternsArray = Object.entries(userPatterns).sort((a, b) => a[0].localeCompare(b[0])); - const deleteIndex = patternsArray.findIndex(([key]) => key === pattern); - patternsArray.splice(deleteIndex, 1); - const updatedPatterns = Object.fromEntries(patternsArray); - - setUserPatterns(updatedPatterns); - - //create new pattern if no other patterns - if (!patternsArray.length) { - return createNewUserPattern(); - } - // // or default to active pattern - // const activePatternID = getActivePattern(); - // const activePatternData = updatedPatterns[activePatternID]; - // if (activePatternData?.code != null) { - // return { pattern: activePatternID, code: activePatternData.code }; - // } - // or find pattern at next index - - const next = patternsArray[deleteIndex]; - if (next != null) { - const [pat, data] = next; - return { pattern: pat, code: data.code }; - } - // or find pattern at previous index - const previous = patternsArray[deleteIndex - 1]; - const [pat, data] = previous; - return { patttern: pat, code: data.code }; -} - -export function createDuplicatePattern(pattern) { - let latestCode = getSetting('latestCode'); - if (!pattern) { - console.warn('cannot duplicate: no pattern selected'); - return; - } - const newPattern = getNextCloneName(pattern); - return { pattern: newPattern, code: latestCode }; -} + setUserPatterns({ ...userPatterns }); + if (id === getActivePattern()) { + setActivePattern(newID); + } + return { id: newID, data }; + }, +}; export async function importPatterns(fileList) { const files = Array.from(fileList); @@ -237,8 +229,8 @@ export async function importPatterns(fileList) { const userPatterns = getUserPatterns() || {}; setUserPatterns({ ...userPatterns, ...JSON.parse(content) }); } else if (file.type === 'text/plain') { - const name = file.name.replace(/\.[^/.]+$/, ''); - addUserPattern(name, { code: content }); + const id = file.name.replace(/\.[^/.]+$/, ''); + userPattern.update(id, { code: content }); } }), ); From 51dd40873314b970cc3b1b618be3d12f0346a94f Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Mon, 8 Jan 2024 00:15:07 -0500 Subject: [PATCH 09/31] all working --- packages/core/repl.mjs | 1 - website/src/repl/Header.jsx | 4 +- website/src/repl/Repl.jsx | 30 +++++---------- website/src/repl/panel/PatternsTab.jsx | 23 +++++------ website/src/settings.mjs | 53 +++++++++++++++----------- 5 files changed, 50 insertions(+), 61 deletions(-) diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index 9fb6b4b9..7c609d66 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -94,7 +94,6 @@ export function repl({ afterEval?.({ code, pattern, meta }); return pattern; } catch (err) { - // console.warn(`[repl] eval error: ${err.message}`); logger(`[eval] error: ${err.message}`, 'error'); updateState({ evalError: err, pending: false }); onEvalError?.(err); diff --git a/website/src/repl/Header.jsx b/website/src/repl/Header.jsx index f806d5d3..748b84e4 100644 --- a/website/src/repl/Header.jsx +++ b/website/src/repl/Header.jsx @@ -19,7 +19,7 @@ export function Header({ context }) { isDirty, activeCode, handleTogglePlay, - handleUpdate, + handleEvaluate, handleShuffle, handleShare, } = context; @@ -85,7 +85,7 @@ export function Header({ context }) { )} )}
- )} + )} */} - {!isExample && ( + {isUserPattern && (
)} updateCodeWindow(id, userPatterns[id]?.code, false)} patterns={userPatterns} started={context.started} activePattern={activePattern} @@ -166,90 +202,24 @@ export function PatternsTab({ context }) { - {featuredPatterns && ( -
-

Featured Patterns

-
- {featuredPatterns.map((pattern) => ( - { - setActivePattern(pattern.hash); - context.handleUpdate(pattern.code, true); - }} - > - - - ))} -
-
- )} - {publicPatterns && ( -
-

Last Creations

-
- {publicPatterns.map((pattern) => ( - { - setActivePattern(pattern.hash); - context.handleUpdate(pattern.code, true); - }} - > - - - ))} -
-
- )} -
+ {Array.from(otherPatterns.keys()).map((key) => { + const patterns = otherPatterns.get(key); -

Examples

- onPatternBtnClick(id, true)} - started={context.started} - patterns={examplePatterns} - activePattern={activePattern} - viewingPattern={viewingPattern} - /> - -

Stock Examples

-
- {Object.entries(tunes).map(([key, tune]) => ( - { - setActivePattern(key); - context.handleUpdate(tune, true); - }} - > - {key} - - ))} -
- -
+ return ( +
+

{key}

+
+ updateCodeWindow(id, patterns[id]?.code, true)} + started={context.started} + patterns={patterns} + activePattern={activePattern} + viewingPattern={viewingPattern} + /> +
+
+ ); + })} ); } - -export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) { - const meta = useMemo(() => getMetadata(pattern.code), [pattern]); - return ( - <> - {pattern.id}. {meta.title || pattern.hash} by {Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous'} - - ); -} diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index 3405e93d..1da4fc65 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -29,8 +29,12 @@ async function loadDBPatterns() { try { const { data: publicPatterns } = await loadPublicPatterns(); const { data: featuredPatterns } = await loadFeaturedPatterns(); - $publicPatterns.set(publicPatterns); - $featuredPatterns.set(featuredPatterns); + 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); } catch (err) { console.error('error loading patterns'); } diff --git a/website/src/settings.mjs b/website/src/settings.mjs index f500b92c..868a2ba8 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -4,7 +4,7 @@ import { useStore } from '@nanostores/react'; import { register } from '@strudel.cycles/core'; import * as tunes from './repl/tunes.mjs'; import { logger } from '@strudel.cycles/core'; - +import { nanoid } from 'nanoid'; export let $publicPatterns = atom([]); export let $featuredPatterns = atom([]); @@ -70,6 +70,14 @@ export function initUserCode(code) { export function useSettings() { const state = useStore(settingsMap); + + const userPatterns = JSON.parse(state.userPatterns); + Object.keys(userPatterns).forEach((key) => { + const data = userPatterns[key]; + data.id = data.id ?? key; + data.date = data.date ?? 0; + userPatterns[key] = data; + }); return { ...state, isZen: [true, 'true'].includes(state.isZen) ? true : false, @@ -82,7 +90,7 @@ export function useSettings() { isFlashEnabled: [true, 'true'].includes(state.isFlashEnabled) ? true : false, fontSize: Number(state.fontSize), panelPosition: state.activeFooter !== '' ? state.panelPosition : 'bottom', // <-- keep this 'bottom' where it is! - userPatterns: JSON.parse(state.userPatterns), + userPatterns: userPatterns, }; } @@ -108,35 +116,23 @@ export const fontSize = patternSetting('fontSize'); export const settingPatterns = { theme, fontFamily, fontSize }; -export function getUserPatterns() { +function getUserPatterns() { return JSON.parse(settingsMap.get().userPatterns); } -function getSetting(key) { - return settingsMap.get()[key]; -} -export function setUserPatterns(obj) { +function setUserPatterns(obj) { return settingsMap.setKey('userPatterns', JSON.stringify(obj)); } export const createPatternID = () => { - const userPatterns = getUserPatterns(); - const date = new Date().toISOString().split('T')[0]; - const todays = Object.entries(userPatterns).filter(([name]) => name.startsWith(date)); - const num = String(todays.length + 1).padStart(3, '0'); - const id = date + '_' + num; - return id; + return nanoid(12); }; export const getNextCloneID = (id) => { - const patterns = { ...userPattern.getAll(), ...examplePattern.getAll() }; - const clones = Object.entries(patterns).filter(([patID]) => patID.startsWith(id)); - const num = String(clones.length + 1).padStart(3, '0'); - const newID = id + '_' + num; - return newID; + return createPatternID(); }; -const examplePatterns = Object.fromEntries(Object.entries(tunes).map(([id, code]) => [id, { code }])); +const examplePatterns = Object.fromEntries(Object.entries(tunes).map(([key, code], i) => [i, { id: i, code }])); export const examplePattern = { getAll() { @@ -154,7 +150,8 @@ export const examplePattern = { // break export const userPattern = { getAll() { - return JSON.parse(settingsMap.get().userPatterns); + const patterns = JSON.parse(settingsMap.get().userPatterns); + return patterns; }, getPatternData(id) { const userPatterns = this.getAll(); @@ -163,10 +160,13 @@ export const userPattern = { exists(id) { return this.getPatternData(id) != null; }, + create() { const newID = createPatternID(); const code = defaultCode; - const data = { code }; + + // const meta = getMetadata + const data = { code, created_at: Date.now(), id: newID }; this.update(newID, data); return { id: newID, data }; }, @@ -221,44 +221,6 @@ export const userPattern = { alert('Name already taken!'); return { id, data }; } -// break -export function updateUserCode(code) { - const userPatterns = getUserPatterns(); - let activePattern = getActivePattern(); - // check if code is that of an example tune - const [example] = Object.entries(tunes).find(([_, tune]) => tune === code) || []; - if (example && (!activePattern || activePattern === example)) { - // select example - setActivePattern(example); - return; - } - const publicPattern = $publicPatterns.get().find((pat) => pat.code === code); - if (publicPattern) { - setActivePattern(publicPattern.hash); - return; - } - const featuredPattern = $featuredPatterns.get().find((pat) => pat.code === code); - if (featuredPattern) { - setActivePattern(featuredPattern.hash); - return; - } - if (!activePattern) { - // create new user pattern - activePattern = newUserPattern(); - setActivePattern(activePattern); - } else if ( - (!!tunes[activePattern] && code !== tunes[activePattern]) || // fork example tune? - $publicPatterns.get().find((p) => p.hash === activePattern) || // fork public pattern? - $featuredPatterns.get().find((p) => p.hash === activePattern) // fork featured pattern? - ) { - // fork example - activePattern = getNextCloneName(activePattern); - setActivePattern(activePattern); - } - setUserPatterns({ ...userPatterns, [activePattern]: { code } }); -} -// break - userPatterns[newID] = data; // copy code delete userPatterns[id]; @@ -270,6 +232,42 @@ export function updateUserCode(code) { }, }; +// export function updateUserCode(code) { +// const userPatterns = getUserPatterns(); +// let activePattern = getActivePattern(); +// // check if code is that of an example tune +// const [example] = Object.entries(tunes).find(([_, tune]) => tune === code) || []; +// if (example && (!activePattern || activePattern === example)) { +// // select example +// setActivePattern(example); +// return; +// } +// const publicPattern = $publicPatterns.get().find((pat) => pat.code === code); +// if (publicPattern) { +// setActivePattern(publicPattern.hash); +// return; +// } +// const featuredPattern = $featuredPatterns.get().find((pat) => pat.code === code); +// if (featuredPattern) { +// setActivePattern(featuredPattern.hash); +// return; +// } +// if (!activePattern) { +// // create new user pattern +// activePattern = newUserPattern(); +// setActivePattern(activePattern); +// } else if ( +// (!!tunes[activePattern] && code !== tunes[activePattern]) || // fork example tune? +// $publicPatterns.get().find((p) => p.hash === activePattern) || // fork public pattern? +// $featuredPatterns.get().find((p) => p.hash === activePattern) // fork featured pattern? +// ) { +// // fork example +// activePattern = getNextCloneName(activePattern); +// setActivePattern(activePattern); +// } +// setUserPatterns({ ...userPatterns, [activePattern]: { code } }); +// } + export async function importPatterns(fileList) { const files = Array.from(fileList); await Promise.all( From 7adfe6f2df2e5fd4750445811783136252627332 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 17 Jan 2024 09:45:00 -0500 Subject: [PATCH 13/31] fixing edge cases... --- website/src/repl/Repl.jsx | 32 +++++++++-------- website/src/repl/panel/PatternsTab.jsx | 48 +++++++++++-------------- website/src/repl/useExamplePatterns.jsx | 34 ++++++++++++++++++ website/src/settings.mjs | 31 +++++++++++----- 4 files changed, 96 insertions(+), 49 deletions(-) create mode 100644 website/src/repl/useExamplePatterns.jsx diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index a02893b3..9bd26e02 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -22,7 +22,6 @@ import { setViewingPattern, createPatternID, userPattern, - examplePattern, getNextCloneID, } from '../settings.mjs'; import { Header } from './Header'; @@ -33,9 +32,12 @@ import { prebake } from './prebake.mjs'; import { getRandomTune, initCode, loadModules, shareCode, ReplContext } from './util.mjs'; import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; import './Repl.css'; +import { useExamplePatterns } from './useExamplePatterns'; const { code: randomTune, name } = getRandomTune(); +// + const { latestCode } = settingsMap.get(); let modulesLoading, presets, drawContext, clearCanvas, isIframe; @@ -47,10 +49,12 @@ if (typeof window !== 'undefined') { clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width); isIframe = window.location !== window.parent.location; } + +let viewingPatternData = { id: '', code: null, collection: userPattern.source }; + export function Repl({ embedded = false }) { const isEmbedded = embedded || isIframe; const { panelPosition, isZen } = useSettings(); - const init = useCallback(() => { const drawTime = [-2, 2]; const drawContext = getDrawContext(); @@ -75,30 +79,29 @@ export function Repl({ embedded = false }) { onUpdateState: (state) => { setReplState({ ...state }); }, - afterEval: ({ code }) => { + afterEval: (all) => { + const { code } = all; setLatestCode(code); - const data = { code }; - let id = getViewingPattern(); window.location.hash = '#' + code2hash(code); - const examplePatternData = examplePattern.getPatternData(id); - const isExamplePattern = examplePatternData != null; + const data = { ...viewingPatternData, code }; + let id = getViewingPattern(); + const isExamplePattern = viewingPatternData.collection != userPattern.source; if (isExamplePattern) { - const codeHasChanged = code !== examplePatternData.code; + const codeHasChanged = code !== viewingPatternData.code; if (codeHasChanged) { // fork example id = getNextCloneID(id); setViewingPattern(id); - userPattern.update(id, data); + viewingPatternData = userPattern.update(id, data).data; } - setActivePattern(id); } else { id = id == null ? createPatternID() : id; - setActivePattern(id); setViewingPattern(id); - userPattern.update(id, data); + viewingPatternData = userPattern.update(id, data).data; } + setActivePattern(id); }, bgFill: false, }); @@ -172,12 +175,13 @@ export function Repl({ embedded = false }) { await prebake(); // declare default samples }; - const handleUpdate = async (id, code, reset = false) => { + const handleUpdate = async (id, data, reset = false) => { + viewingPatternData = data; if (reset) { await resetEditor(); } setViewingPattern(id); - editorRef.current.setCode(code); + editorRef.current.setCode(data.code); }; const handleEvaluate = () => { diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index 3276a16b..60ca4465 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -14,9 +14,9 @@ import { import { useMemo } from 'react'; -import * as tunes from '../tunes.mjs'; import { useStore } from '@nanostores/react'; import { getMetadata } from '../../metadata_parser'; +import { useExamplePatterns } from '../useExamplePatterns'; function classNames(...classes) { return classes.filter(Boolean).join(' '); @@ -90,27 +90,21 @@ function PatternButtons({ patterns, activePattern, onClick, viewingPattern, star } export function PatternsTab({ context }) { - const { userPatterns } = useSettings(); const activePattern = useActivePattern(); - - const featuredPatterns = useStore($featuredPatterns); - const publicPatterns = useStore($publicPatterns); - - // const otherPatterns = [ - // {source: 'Stock Examples', patterns: examplePatterns } - // ] - - const otherPatterns = useMemo(() => { - const pats = new Map(); - pats.set('Featured', featuredPatterns); - pats.set('Last Creations', publicPatterns); - pats.set('Stock Examples', examplePattern.getAll()); - return pats; - }, [featuredPatterns, publicPatterns]); - const viewingPattern = useViewingPattern(); - const updateCodeWindow = (id, code, reset = false) => { - context.handleUpdate(id, code, reset); + const { userPatterns } = useSettings(); + const examplePatterns = useExamplePatterns(); + const collections = examplePatterns.collections; + const examplesData = examplePatterns.patterns; + + const updateCodeWindow = (id, data, reset = false) => { + context.handleUpdate(id, data, reset); + // if (patternSource === userPattern.source) { + + // } else { + // const source = otherPatterns.get(patternSource); + // const data = source[id]; + // } }; const isUserPattern = userPatterns[viewingPattern] != null; @@ -151,7 +145,7 @@ export function PatternsTab({ context }) { className="hover:opacity-50" onClick={() => { const { id, data } = userPattern.delete(viewingPattern); - updateCodeWindow(id, data.code); + updateCodeWindow(id, { ...data, collection: userPattern.source }); }} title="Delete" > @@ -162,7 +156,7 @@ export function PatternsTab({ context }) { )} updateCodeWindow(id, userPatterns[id]?.code, false)} + onClick={(id) => updateCodeWindow(id, { ...userPatterns[id], collection: userPattern.source }, false)} patterns={userPatterns} started={context.started} activePattern={activePattern} @@ -202,15 +196,15 @@ export function PatternsTab({ context }) { - {Array.from(otherPatterns.keys()).map((key) => { - const patterns = otherPatterns.get(key); + {Array.from(collections.keys()).map((collection) => { + const patterns = collections.get(collection); return ( -
-

{key}

+
+

{collection}

updateCodeWindow(id, patterns[id]?.code, true)} + onClick={(id) => updateCodeWindow(id, { ...patterns[id], collection }, true)} started={context.started} patterns={patterns} activePattern={activePattern} diff --git a/website/src/repl/useExamplePatterns.jsx b/website/src/repl/useExamplePatterns.jsx new file mode 100644 index 00000000..f51d9091 --- /dev/null +++ b/website/src/repl/useExamplePatterns.jsx @@ -0,0 +1,34 @@ +import { examplePattern, $featuredPatterns, $publicPatterns } from '../settings.mjs'; +import { useStore } from '@nanostores/react'; +import { useCallback, useMemo } from 'react'; + +export const useExamplePatterns = () => { + const featuredPatterns = useStore($featuredPatterns); + const publicPatterns = useStore($publicPatterns); + const collections = useMemo(() => { + const pats = new Map(); + pats.set('Featured', featuredPatterns); + pats.set('Last Creations', publicPatterns); + pats.set(examplePattern.source, examplePattern.getAll()); + return pats; + }, [featuredPatterns, publicPatterns]); + + const patterns = useMemo(() => { + const allPatterns = Object.assign({}, ...collections.values()); + return allPatterns; + }, [collections]); + + // const examplePatterns = examplePattern.getAll(); + + // const collections = new Map(); + // collections.set('Featured', featuredPatterns); + // collections.set('Last Creations', publicPatterns); + // collections.set(examplePattern.source, examplePatterns); + // const patterns = { + // ...examplePatterns, + // ...publicPatterns, + // ...examplePatterns, + // }; + + return { patterns, collections }; +}; diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 868a2ba8..9f2da640 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -46,9 +46,22 @@ export function getViewingPattern() { export function useViewingPattern() { return useStore($viewingPattern); } + +// const $viewingCollection = persistentAtom('viewingCollection', '', { listen: false }); +// export function setViewingCollection(key) { +// $viewingCollection.set(key); +// } +// export function getViewingCollection() { +// return $viewingCollection.get(); +// } + +// export function useViewingCollection() { +// return useStore($viewingCollection); +// } // active pattern is separate, because it shouldn't sync state across tabs // reason: https://github.com/tidalcycles/strudel/issues/857 const $activePattern = persistentAtom('activePattern', '', { listen: false }); + export function setActivePattern(key) { $activePattern.set(key); } @@ -135,6 +148,7 @@ export const getNextCloneID = (id) => { const examplePatterns = Object.fromEntries(Object.entries(tunes).map(([key, code], i) => [i, { id: i, code }])); export const examplePattern = { + source: 'Stock Examples', getAll() { return examplePatterns; }, @@ -149,6 +163,8 @@ export const examplePattern = { // break export const userPattern = { + source: 'user', + collection: 'user', getAll() { const patterns = JSON.parse(settingsMap.get().userPatterns); return patterns; @@ -164,22 +180,20 @@ export const userPattern = { create() { const newID = createPatternID(); const code = defaultCode; - - // const meta = getMetadata - const data = { code, created_at: Date.now(), id: newID }; - this.update(newID, data); - return { id: newID, data }; + const data = { code, created_at: Date.now(), id: newID, collection: this.collection }; + return this.update(newID, data); }, update(id, data) { const userPatterns = this.getAll(); + data = { ...data, id, collection: this.collection }; setUserPatterns({ ...userPatterns, [id]: data }); + return { id, data }; }, duplicate(id) { const examplePatternData = examplePattern.getPatternData(id); const data = examplePatternData != null ? examplePatternData : this.getPatternData(id); const newID = getNextCloneID(id); - this.update(newID, data); - return { id: newID, data }; + return this.update(newID, data); }, clearAll() { if (!confirm(`This will delete all your patterns. Are you really sure?`)) { @@ -193,7 +207,8 @@ export const userPattern = { } // setViewingPattern(null); setActivePattern(null); - return { id: null, data: { code: defaultCode } }; + + return { id: null, data: { code: defaultCode, id: null, collection: this.collection } }; }, delete(id) { const userPatterns = this.getAll(); From 6cb156d876aff63f965f611dda51529739fa185c Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 18 Jan 2024 20:01:54 -0500 Subject: [PATCH 14/31] cleaning up --- website/src/repl/Repl.jsx | 28 ++- website/src/repl/panel/PatternsTab.jsx | 129 ++++--------- website/src/repl/useExamplePatterns.jsx | 23 +-- website/src/repl/util.mjs | 28 --- website/src/settings.mjs | 241 +----------------------- website/src/user_pattern_utils.mjs | 190 +++++++++++++++++++ 6 files changed, 249 insertions(+), 390 deletions(-) create mode 100644 website/src/user_pattern_utils.mjs diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 9bd26e02..8ec9feb2 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -12,18 +12,17 @@ import { defaultAudioDeviceName } from '../settings.mjs'; import { getAudioDevices, setAudioDevice } from './util.mjs'; import { StrudelMirror, defaultSettings } from '@strudel/codemirror'; import { useCallback, useEffect, useRef, useState } from 'react'; +import { settingsMap, useSettings } from '../settings.mjs'; import { initUserCode, setActivePattern, setLatestCode, - settingsMap, - useSettings, - getViewingPattern, setViewingPattern, createPatternID, userPattern, - getNextCloneID, -} from '../settings.mjs'; + getViewingPatternData, + setViewingPatternData, +} from '../user_pattern_utils.mjs'; import { Header } from './Header'; import Loader from './Loader'; import { Panel } from './panel/Panel'; @@ -32,12 +31,8 @@ import { prebake } from './prebake.mjs'; import { getRandomTune, initCode, loadModules, shareCode, ReplContext } from './util.mjs'; import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; import './Repl.css'; -import { useExamplePatterns } from './useExamplePatterns'; const { code: randomTune, name } = getRandomTune(); - -// - const { latestCode } = settingsMap.get(); let modulesLoading, presets, drawContext, clearCanvas, isIframe; @@ -50,8 +45,6 @@ if (typeof window !== 'undefined') { isIframe = window.location !== window.parent.location; } -let viewingPatternData = { id: '', code: null, collection: userPattern.source }; - export function Repl({ embedded = false }) { const isEmbedded = embedded || isIframe; const { panelPosition, isZen } = useSettings(); @@ -83,23 +76,24 @@ export function Repl({ embedded = false }) { const { code } = all; setLatestCode(code); window.location.hash = '#' + code2hash(code); + const viewingPatternData = getViewingPatternData(); const data = { ...viewingPatternData, code }; - let id = getViewingPattern(); - const isExamplePattern = viewingPatternData.collection != userPattern.source; + let id = data.id; + const isExamplePattern = viewingPatternData.collection !== userPattern.collection; if (isExamplePattern) { const codeHasChanged = code !== viewingPatternData.code; if (codeHasChanged) { // fork example - id = getNextCloneID(id); + id = createPatternID(); setViewingPattern(id); - viewingPatternData = userPattern.update(id, data).data; + setViewingPatternData(userPattern.update(id, data).data); } } else { id = id == null ? createPatternID() : id; setViewingPattern(id); - viewingPatternData = userPattern.update(id, data).data; + setViewingPatternData(userPattern.update(id, data).data); } setActivePattern(id); }, @@ -176,7 +170,7 @@ export function Repl({ embedded = false }) { }; const handleUpdate = async (id, data, reset = false) => { - viewingPatternData = data; + setViewingPatternData(data); if (reset) { await resetEditor(); } diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index 60ca4465..a950d754 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -1,20 +1,13 @@ -import { DocumentDuplicateIcon, PencilSquareIcon, TrashIcon } from '@heroicons/react/20/solid'; - +import { DocumentDuplicateIcon, TrashIcon } from '@heroicons/react/20/solid'; +import { useSettings } from '../../settings.mjs'; import { - $featuredPatterns, - $publicPatterns, exportPatterns, importPatterns, useActivePattern, useViewingPattern, - useSettings, userPattern, - examplePattern, -} from '../../settings.mjs'; - +} from '../../user_pattern_utils.mjs'; import { useMemo } from 'react'; - -import { useStore } from '@nanostores/react'; import { getMetadata } from '../../metadata_parser'; import { useExamplePatterns } from '../useExamplePatterns'; @@ -24,19 +17,14 @@ function classNames(...classes) { function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) { const meta = useMemo(() => getMetadata(pattern.code), [pattern]); + return ( - <>{`${pattern.id}: ${meta.title ?? pattern.hash ?? 'unnamed'} by ${ - Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous' - }`} + <>{`${pattern.id}: ${ + meta.title ?? pattern.hash ?? new Date(pattern.created_at).toLocaleDateString() ?? 'unnamed' + } by ${Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous'}`} ); } -const getPatternLabel = (pattern) => { - return `${pattern.id}: ${meta.title ?? pattern.hash ?? 'unnamed'} by ${ - Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous' - }`; -}; - function PatternButton({ showOutline, onClick, pattern, showHiglight }) { return ( { -// setActivePattern(pattern.hash); -// context.handleUpdate(pattern.code, true); -// }} -// > -// -// -// ); -// } - function PatternButtons({ patterns, activePattern, onClick, viewingPattern, started }) { return (
@@ -89,28 +59,27 @@ function PatternButtons({ patterns, activePattern, onClick, viewingPattern, star ); } +function ActionButton({ children, onClick, label, labelIsHidden }) { + return ( + + ); +} + export function PatternsTab({ context }) { const activePattern = useActivePattern(); const viewingPattern = useViewingPattern(); const { userPatterns } = useSettings(); const examplePatterns = useExamplePatterns(); const collections = examplePatterns.collections; - const examplesData = examplePatterns.patterns; const updateCodeWindow = (id, data, reset = false) => { context.handleUpdate(id, data, reset); - // if (patternSource === userPattern.source) { - - // } else { - // const source = otherPatterns.get(patternSource); - // const data = source[id]; - // } }; - const isUserPattern = userPatterns[viewingPattern] != null; - // const isExample = useMemo(() => activePattern && !!tunes[activePattern], [activePattern]); - return (
@@ -118,69 +87,56 @@ export function PatternsTab({ context }) {

{`${viewingPattern}`}

- {/* {!isExample && ( - - )} */} - + {isUserPattern && ( - + )}
)} updateCodeWindow(id, { ...userPatterns[id], collection: userPattern.source }, false)} + onClick={(id) => updateCodeWindow(id, { ...userPatterns[id], collection: userPattern.collection }, false)} patterns={userPatterns} started={context.started} activePattern={activePattern} viewingPattern={viewingPattern} />
- - + /> + - +
{Array.from(collections.keys()).map((collection) => { const patterns = collections.get(collection); - return (

{collection}

diff --git a/website/src/repl/useExamplePatterns.jsx b/website/src/repl/useExamplePatterns.jsx index f51d9091..65d94583 100644 --- a/website/src/repl/useExamplePatterns.jsx +++ b/website/src/repl/useExamplePatterns.jsx @@ -1,34 +1,23 @@ -import { examplePattern, $featuredPatterns, $publicPatterns } from '../settings.mjs'; +import { $featuredPatterns, $publicPatterns } from '../user_pattern_utils.mjs'; import { useStore } from '@nanostores/react'; -import { useCallback, useMemo } from 'react'; +import { useMemo } from 'react'; +import * as tunes from '../repl/tunes.mjs'; export const useExamplePatterns = () => { const featuredPatterns = useStore($featuredPatterns); const publicPatterns = useStore($publicPatterns); const collections = useMemo(() => { + const stockPatterns = Object.fromEntries(Object.entries(tunes).map(([key, code], i) => [i, { id: i, code }])); const pats = new Map(); pats.set('Featured', featuredPatterns); pats.set('Last Creations', publicPatterns); - pats.set(examplePattern.source, examplePattern.getAll()); + pats.set('Stock Examples', stockPatterns); return pats; }, [featuredPatterns, publicPatterns]); const patterns = useMemo(() => { - const allPatterns = Object.assign({}, ...collections.values()); - return allPatterns; + return Object.assign({}, ...collections.values()); }, [collections]); - // const examplePatterns = examplePattern.getAll(); - - // const collections = new Map(); - // collections.set('Featured', featuredPatterns); - // collections.set('Last Creations', publicPatterns); - // collections.set(examplePattern.source, examplePatterns); - // const patterns = { - // ...examplePatterns, - // ...publicPatterns, - // ...examplePatterns, - // }; - return { patterns, collections }; }; diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index 1da4fc65..19afdabb 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -9,7 +9,6 @@ import { createClient } from '@supabase/supabase-js'; import { nanoid } from 'nanoid'; import { writeText } from '@tauri-apps/api/clipboard'; import { createContext } from 'react'; -import { $publicPatterns, $featuredPatterns } from '../settings.mjs'; // Create a single supabase client for interacting with your database export const supabase = createClient( @@ -17,33 +16,6 @@ export const supabase = createClient( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', ); -export function loadPublicPatterns() { - return supabase.from('code').select().eq('public', true).limit(20).order('id', { ascending: false }); -} - -export function loadFeaturedPatterns() { - return supabase.from('code').select().eq('featured', true).limit(20).order('id', { ascending: false }); -} - -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); - } catch (err) { - console.error('error loading patterns'); - } -} - -if (typeof window !== 'undefined') { - loadDBPatterns(); -} - export async function initCode() { // load code from url hash (either short hash from database or decode long hash) try { diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 9f2da640..8815a215 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -1,12 +1,6 @@ -import { atom } from 'nanostores'; -import { persistentMap, persistentAtom } from '@nanostores/persistent'; +import { persistentMap } from '@nanostores/persistent'; import { useStore } from '@nanostores/react'; import { register } from '@strudel.cycles/core'; -import * as tunes from './repl/tunes.mjs'; -import { logger } from '@strudel.cycles/core'; -import { nanoid } from 'nanoid'; -export let $publicPatterns = atom([]); -export let $featuredPatterns = atom([]); export const defaultAudioDeviceName = 'System Standard'; @@ -33,54 +27,6 @@ export const defaultSettings = { export const settingsMap = persistentMap('strudel-settings', defaultSettings); -const defaultCode = ''; -//pattern that the use is currently viewing in the window -const $viewingPattern = persistentAtom('viewingPattern', '', { listen: false }); -export function setViewingPattern(key) { - $viewingPattern.set(key); -} -export function getViewingPattern() { - return $viewingPattern.get(); -} - -export function useViewingPattern() { - return useStore($viewingPattern); -} - -// const $viewingCollection = persistentAtom('viewingCollection', '', { listen: false }); -// export function setViewingCollection(key) { -// $viewingCollection.set(key); -// } -// export function getViewingCollection() { -// return $viewingCollection.get(); -// } - -// export function useViewingCollection() { -// return useStore($viewingCollection); -// } -// active pattern is separate, because it shouldn't sync state across tabs -// reason: https://github.com/tidalcycles/strudel/issues/857 -const $activePattern = persistentAtom('activePattern', '', { listen: false }); - -export function setActivePattern(key) { - $activePattern.set(key); -} -export function getActivePattern() { - return $activePattern.get(); -} -export function useActivePattern() { - return useStore($activePattern); -} -export function initUserCode(code) { - const patterns = { ...userPattern.getAll(), ...examplePattern.getAll() }; - const match = Object.entries(patterns).find(([_, pat]) => pat.code === code); - const id = match?.[0]; - if (id != null) { - setActivePattern(id); - setViewingPattern(id); - } -} - export function useSettings() { const state = useStore(settingsMap); @@ -88,7 +34,6 @@ export function useSettings() { Object.keys(userPatterns).forEach((key) => { const data = userPatterns[key]; data.id = data.id ?? key; - data.date = data.date ?? 0; userPatterns[key] = data; }); return { @@ -109,7 +54,6 @@ export function useSettings() { export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab); -export const setLatestCode = (code) => settingsMap.setKey('latestCode', code); export const setIsZen = (active) => settingsMap.setKey('isZen', !!active); const patternSetting = (key) => @@ -128,186 +72,3 @@ export const fontFamily = patternSetting('fontFamily'); export const fontSize = patternSetting('fontSize'); export const settingPatterns = { theme, fontFamily, fontSize }; - -function getUserPatterns() { - return JSON.parse(settingsMap.get().userPatterns); -} - -function setUserPatterns(obj) { - return settingsMap.setKey('userPatterns', JSON.stringify(obj)); -} - -export const createPatternID = () => { - return nanoid(12); -}; - -export const getNextCloneID = (id) => { - return createPatternID(); -}; - -const examplePatterns = Object.fromEntries(Object.entries(tunes).map(([key, code], i) => [i, { id: i, code }])); - -export const examplePattern = { - source: 'Stock Examples', - getAll() { - return examplePatterns; - }, - getPatternData(id) { - const pats = this.getAll(); - return pats[id]; - }, - exists(id) { - return this.getPatternData(id) != null; - }, -}; - -// break -export const userPattern = { - source: 'user', - collection: 'user', - getAll() { - const patterns = JSON.parse(settingsMap.get().userPatterns); - return patterns; - }, - getPatternData(id) { - const userPatterns = this.getAll(); - return userPatterns[id]; - }, - exists(id) { - return this.getPatternData(id) != null; - }, - - create() { - const newID = createPatternID(); - const code = defaultCode; - const data = { code, created_at: Date.now(), id: newID, collection: this.collection }; - return this.update(newID, data); - }, - update(id, data) { - const userPatterns = this.getAll(); - data = { ...data, id, collection: this.collection }; - setUserPatterns({ ...userPatterns, [id]: data }); - return { id, data }; - }, - duplicate(id) { - const examplePatternData = examplePattern.getPatternData(id); - const data = examplePatternData != null ? examplePatternData : this.getPatternData(id); - const newID = getNextCloneID(id); - return this.update(newID, data); - }, - clearAll() { - if (!confirm(`This will delete all your patterns. Are you really sure?`)) { - return; - } - const viewingPattern = getViewingPattern(); - const examplePatternData = examplePattern.getPatternData(viewingPattern); - setUserPatterns({}); - if (examplePatternData != null) { - return { id: viewingPattern, data: examplePatternData }; - } - // setViewingPattern(null); - setActivePattern(null); - - return { id: null, data: { code: defaultCode, id: null, collection: this.collection } }; - }, - delete(id) { - const userPatterns = this.getAll(); - delete userPatterns[id]; - if (getActivePattern() === id) { - setActivePattern(null); - } - setUserPatterns(userPatterns); - const viewingPattern = getViewingPattern(); - if (viewingPattern === id) { - return { id: null, data: { code: defaultCode } }; - } - return { id: viewingPattern, data: userPatterns[viewingPattern] }; - }, - - rename(id) { - const userPatterns = this.getAll(); - const newID = prompt('Enter new name', id); - const data = userPatterns[id]; - if (newID === null) { - // canceled - return { id, data }; - } - if (userPatterns[newID]) { - alert('Name already taken!'); - return { id, data }; - } - userPatterns[newID] = data; // copy code - delete userPatterns[id]; - - setUserPatterns({ ...userPatterns }); - if (id === getActivePattern()) { - setActivePattern(newID); - } - return { id: newID, data }; - }, -}; - -// export function updateUserCode(code) { -// const userPatterns = getUserPatterns(); -// let activePattern = getActivePattern(); -// // check if code is that of an example tune -// const [example] = Object.entries(tunes).find(([_, tune]) => tune === code) || []; -// if (example && (!activePattern || activePattern === example)) { -// // select example -// setActivePattern(example); -// return; -// } -// const publicPattern = $publicPatterns.get().find((pat) => pat.code === code); -// if (publicPattern) { -// setActivePattern(publicPattern.hash); -// return; -// } -// const featuredPattern = $featuredPatterns.get().find((pat) => pat.code === code); -// if (featuredPattern) { -// setActivePattern(featuredPattern.hash); -// return; -// } -// if (!activePattern) { -// // create new user pattern -// activePattern = newUserPattern(); -// setActivePattern(activePattern); -// } else if ( -// (!!tunes[activePattern] && code !== tunes[activePattern]) || // fork example tune? -// $publicPatterns.get().find((p) => p.hash === activePattern) || // fork public pattern? -// $featuredPatterns.get().find((p) => p.hash === activePattern) // fork featured pattern? -// ) { -// // fork example -// activePattern = getNextCloneName(activePattern); -// setActivePattern(activePattern); -// } -// setUserPatterns({ ...userPatterns, [activePattern]: { code } }); -// } - -export async function importPatterns(fileList) { - const files = Array.from(fileList); - await Promise.all( - files.map(async (file, i) => { - const content = await file.text(); - if (file.type === 'application/json') { - const userPatterns = getUserPatterns() || {}; - setUserPatterns({ ...userPatterns, ...JSON.parse(content) }); - } else if (file.type === 'text/plain') { - const id = file.name.replace(/\.[^/.]+$/, ''); - userPattern.update(id, { code: content }); - } - }), - ); - logger(`import done!`); -} - -export async function exportPatterns() { - const userPatterns = getUserPatterns() || {}; - const blob = new Blob([JSON.stringify(userPatterns)], { type: 'application/json' }); - const downloadLink = document.createElement('a'); - downloadLink.href = window.URL.createObjectURL(blob); - const date = new Date().toISOString().split('T')[0]; - downloadLink.download = `strudel_patterns_${date}.json`; - document.body.appendChild(downloadLink); - downloadLink.click(); - document.body.removeChild(downloadLink); -} diff --git a/website/src/user_pattern_utils.mjs b/website/src/user_pattern_utils.mjs new file mode 100644 index 00000000..70b64dc1 --- /dev/null +++ b/website/src/user_pattern_utils.mjs @@ -0,0 +1,190 @@ +import { atom } from 'nanostores'; +import { persistentAtom } from '@nanostores/persistent'; +import { useStore } from '@nanostores/react'; + +import { logger } from '@strudel.cycles/core'; +import { nanoid } from 'nanoid'; +import { settingsMap } from './settings.mjs'; +import { supabase } from './repl/util.mjs'; + +export let $publicPatterns = atom([]); +export let $featuredPatterns = atom([]); +const userPatternCollectionName = 'user'; +export let $viewingPatternData = atom({ id: null, code: null, collection: userPatternCollectionName }); + +export const getViewingPatternData = () => { + return $viewingPatternData.get(); +}; + +export const setViewingPatternData = (data) => { + $viewingPatternData.set(data); +}; + +export function loadPublicPatterns() { + return supabase.from('code').select().eq('public', true).limit(20).order('id', { ascending: false }); +} + +export function loadFeaturedPatterns() { + return supabase.from('code').select().eq('featured', true).limit(20).order('id', { ascending: false }); +} + +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); + } catch (err) { + console.error('error loading patterns'); + } +} + +if (typeof window !== 'undefined') { + loadDBPatterns(); +} + +//pattern that the use is currently viewing in the window +const $viewingPattern = persistentAtom('viewingPattern', '', { listen: false }); +export function setViewingPattern(key) { + $viewingPattern.set(key); +} +export function getViewingPattern() { + return $viewingPattern.get(); +} + +export function useViewingPattern() { + return useStore($viewingPattern); +} +// active pattern is separate, because it shouldn't sync state across tabs +// reason: https://github.com/tidalcycles/strudel/issues/857 +const $activePattern = persistentAtom('activePattern', '', { listen: false }); + +export function setActivePattern(key) { + $activePattern.set(key); +} +export function getActivePattern() { + return $activePattern.get(); +} +export function useActivePattern() { + return useStore($activePattern); +} +export function initUserCode(code) { + const patterns = { ...userPattern.getAll() }; + const match = Object.entries(patterns).find(([_, pat]) => pat.code === code); + const id = match?.[0]; + if (id != null) { + setActivePattern(id); + setViewingPattern(id); + } +} + +export const setLatestCode = (code) => settingsMap.setKey('latestCode', code); + +const defaultCode = ''; +export const userPattern = { + collection: userPatternCollectionName, + getAll() { + const patterns = JSON.parse(settingsMap.get().userPatterns); + return patterns ?? {}; + }, + getPatternData(id) { + const userPatterns = this.getAll(); + return userPatterns[id]; + }, + exists(id) { + return this.getPatternData(id) != null; + }, + + create() { + const newID = createPatternID(); + const code = defaultCode; + const data = { code, created_at: Date.now(), id: newID, collection: this.collection }; + return { id: newID, data }; + }, + createAndAddToDB() { + const newPattern = this.create(); + this.update(newPattern.id, newPattern.data); + }, + + update(id, data) { + const userPatterns = this.getAll(); + data = { ...data, id, collection: this.collection }; + setUserPatterns({ ...userPatterns, [id]: data }); + return { id, data }; + }, + duplicate(data) { + const newID = createPatternID(); + return this.update(newID, data); + }, + clearAll() { + if (!confirm(`This will delete all your patterns. Are you really sure?`)) { + return; + } + const viewingPatternData = getViewingPatternData(); + setUserPatterns({}); + + if (viewingPatternData.collection !== this.collection) { + return { id: viewingPatternData.id, data: viewingPatternData }; + } + setActivePattern(null); + return this.create(); + }, + delete(id) { + const userPatterns = this.getAll(); + delete userPatterns[id]; + if (getActivePattern() === id) { + setActivePattern(null); + } + setUserPatterns(userPatterns); + const viewingPattern = getViewingPattern(); + if (viewingPattern === id) { + return { id: null, data: { code: defaultCode } }; + } + return { id: viewingPattern, data: userPatterns[viewingPattern] }; + }, +}; + +function setUserPatterns(obj) { + return settingsMap.setKey('userPatterns', JSON.stringify(obj)); +} + +export const createPatternID = () => { + return nanoid(12); +}; + +export const getNextCloneID = (id) => { + return createPatternID(); +}; + +export async function importPatterns(fileList) { + const files = Array.from(fileList); + await Promise.all( + files.map(async (file, i) => { + const content = await file.text(); + if (file.type === 'application/json') { + const userPatterns = userPattern.getAll(); + setUserPatterns({ ...userPatterns, ...JSON.parse(content) }); + } else if (file.type === 'text/plain') { + const id = file.name.replace(/\.[^/.]+$/, ''); + userPattern.update(id, { code: content }); + } + }), + ); + logger(`import done!`); +} + +export async function exportPatterns() { + const userPatterns = userPattern.getAll(); + const blob = new Blob([JSON.stringify(userPatterns)], { type: 'application/json' }); + const downloadLink = document.createElement('a'); + downloadLink.href = window.URL.createObjectURL(blob); + const date = new Date().toISOString().split('T')[0]; + downloadLink.download = `strudel_patterns_${date}.json`; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); +} From 77a48e835191210d93024d249923255329be09c2 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 18 Jan 2024 22:30:53 -0500 Subject: [PATCH 15/31] persistent pattern data --- website/src/user_pattern_utils.mjs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/website/src/user_pattern_utils.mjs b/website/src/user_pattern_utils.mjs index 70b64dc1..7419a5a7 100644 --- a/website/src/user_pattern_utils.mjs +++ b/website/src/user_pattern_utils.mjs @@ -10,14 +10,24 @@ import { supabase } from './repl/util.mjs'; export let $publicPatterns = atom([]); export let $featuredPatterns = atom([]); const userPatternCollectionName = 'user'; -export let $viewingPatternData = atom({ id: null, code: null, collection: userPatternCollectionName }); +export let $viewingPatternData = persistentAtom( + 'viewingPatternData', + { + id: '', + code: '', + collection: userPatternCollectionName, + created_at: Date.now(), + }, + { listen: false }, +); export const getViewingPatternData = () => { + console.log(JSON.parse($viewingPatternData.get())); return $viewingPatternData.get(); }; export const setViewingPatternData = (data) => { - $viewingPatternData.set(data); + $viewingPatternData.set(JSON.stringify(data)); }; export function loadPublicPatterns() { @@ -107,7 +117,7 @@ export const userPattern = { }, createAndAddToDB() { const newPattern = this.create(); - this.update(newPattern.id, newPattern.data); + return this.update(newPattern.id, newPattern.data); }, update(id, data) { From 9a13c58583ed3084ee9f631aea55a4fe51676448 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Fri, 19 Jan 2024 00:14:08 -0500 Subject: [PATCH 16/31] cleaning up --- website/src/repl/Repl.jsx | 27 ++++----- website/src/repl/panel/PatternsTab.jsx | 74 ++++++++++++----------- website/src/repl/useExamplePatterns.jsx | 13 ++-- website/src/repl/util.mjs | 14 +++-- website/src/user_pattern_utils.mjs | 80 +++++++++++++------------ 5 files changed, 113 insertions(+), 95 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 8ec9feb2..ce4561b8 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -17,7 +17,6 @@ import { initUserCode, setActivePattern, setLatestCode, - setViewingPattern, createPatternID, userPattern, getViewingPatternData, @@ -77,7 +76,6 @@ export function Repl({ embedded = false }) { setLatestCode(code); window.location.hash = '#' + code2hash(code); const viewingPatternData = getViewingPatternData(); - const data = { ...viewingPatternData, code }; let id = data.id; const isExamplePattern = viewingPatternData.collection !== userPattern.collection; @@ -86,13 +84,12 @@ export function Repl({ embedded = false }) { const codeHasChanged = code !== viewingPatternData.code; if (codeHasChanged) { // fork example - id = createPatternID(); - setViewingPattern(id); - setViewingPatternData(userPattern.update(id, data).data); + const newPattern = userPattern.duplicate(data); + id = newPattern.id; + setViewingPatternData(newPattern.data); } } else { - id = id == null ? createPatternID() : id; - setViewingPattern(id); + id = userPattern.isValidID(id) ? id : createPatternID(); setViewingPatternData(userPattern.update(id, data).data); } setActivePattern(id); @@ -169,23 +166,23 @@ export function Repl({ embedded = false }) { await prebake(); // declare default samples }; - const handleUpdate = async (id, data, reset = false) => { - setViewingPatternData(data); + const handleUpdate = async (patternData, reset = false) => { if (reset) { await resetEditor(); } - setViewingPattern(id); - editorRef.current.setCode(data.code); + setViewingPatternData(patternData); + editorRef.current.setCode(patternData.code); }; const handleEvaluate = () => { editorRef.current.evaluate(); }; const handleShuffle = async () => { - const { code, name } = getRandomTune(); - logger(`[repl] ✨ loading random tune "${name}"`); - setActivePattern(name); - setViewingPattern(name); + const patternData = getRandomTune(); + const code = patternData.code; + logger(`[repl] ✨ loading random tune "${patternData.id}"`); + setActivePattern(patternData.id); + setViewingPatternData(patternData); clearCanvas(); resetLoadedSounds(); await prebake(); // declare default samples diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index a950d754..8c66d2ea 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -4,7 +4,7 @@ import { exportPatterns, importPatterns, useActivePattern, - useViewingPattern, + useViewingPatternData, userPattern, } from '../../user_pattern_utils.mjs'; import { useMemo } from 'react'; @@ -40,21 +40,26 @@ function PatternButton({ showOutline, onClick, pattern, showHiglight }) { ); } -function PatternButtons({ patterns, activePattern, onClick, viewingPattern, started }) { +function PatternButtons({ patterns, activePattern, onClick, started }) { + const viewingPatternStore = useViewingPatternData(); + const viewingPatternData = JSON.parse(viewingPatternStore); + const viewingPatternID = viewingPatternData.id; return (
- {Object.values(patterns).map((pattern) => { - const id = pattern.id; - return ( - onClick(id)} - /> - ); - })} + {Object.values(patterns) + .reverse() + .map((pattern) => { + const id = pattern.id; + return ( + onClick(id)} + /> + ); + })}
); } @@ -70,30 +75,32 @@ function ActionButton({ children, onClick, label, labelIsHidden }) { export function PatternsTab({ context }) { const activePattern = useActivePattern(); - const viewingPattern = useViewingPattern(); + const viewingPatternStore = useViewingPatternData(); + const viewingPatternData = JSON.parse(viewingPatternStore); + const { userPatterns } = useSettings(); const examplePatterns = useExamplePatterns(); const collections = examplePatterns.collections; - const updateCodeWindow = (id, data, reset = false) => { - context.handleUpdate(id, data, reset); + const updateCodeWindow = (patternData, reset = false) => { + context.handleUpdate(patternData, reset); }; - const isUserPattern = userPatterns[viewingPattern] != null; + const viewingPatternID = viewingPatternData?.id; + const viewingIDIsValid = userPattern.isValidID(viewingPatternID); + const isUserPattern = userPatterns[viewingPatternID] != null; return (
- {viewingPattern && ( + {viewingIDIsValid && (
-

{`${viewingPattern}`}

+

{`${viewingPatternID}`}

{ - const { id, data } = userPattern.duplicate( - userPattern.getPatternData(id) ?? examplePatterns.patterns[id], - ); - updateCodeWindow(id, data); + const { data } = userPattern.duplicate(viewingPatternData); + updateCodeWindow(data); }} labelIsHidden > @@ -103,8 +110,8 @@ export function PatternsTab({ context }) { { - const { id, data } = userPattern.delete(viewingPattern); - updateCodeWindow(id, { ...data, collection: userPattern.collection }); + const { data } = userPattern.delete(viewingPatternID); + updateCodeWindow({ ...data, collection: userPattern.collection }); }} labelIsHidden > @@ -115,25 +122,25 @@ export function PatternsTab({ context }) {
)} updateCodeWindow(id, { ...userPatterns[id], collection: userPattern.collection }, false)} + onClick={(id) => updateCodeWindow({ ...userPatterns[id], collection: userPattern.collection }, false)} patterns={userPatterns} started={context.started} activePattern={activePattern} - viewingPattern={viewingPattern} + viewingPatternID={viewingPatternID} />
{ - const { id, data } = userPattern.createAndAddToDB(); - updateCodeWindow(id, data); + const { data } = userPattern.createAndAddToDB(); + updateCodeWindow(data); }} /> { - const { id, data } = userPattern.clearAll(); - updateCodeWindow(id, data); + const { data } = userPattern.clearAll(); + updateCodeWindow(data); }} /> @@ -157,11 +164,10 @@ export function PatternsTab({ context }) {

{collection}

updateCodeWindow(id, { ...patterns[id], collection }, true)} + onClick={(id) => updateCodeWindow({ ...patterns[id], collection }, true)} started={context.started} patterns={patterns} activePattern={activePattern} - viewingPattern={viewingPattern} />
diff --git a/website/src/repl/useExamplePatterns.jsx b/website/src/repl/useExamplePatterns.jsx index 65d94583..92874db6 100644 --- a/website/src/repl/useExamplePatterns.jsx +++ b/website/src/repl/useExamplePatterns.jsx @@ -1,17 +1,20 @@ -import { $featuredPatterns, $publicPatterns } from '../user_pattern_utils.mjs'; +import { $featuredPatterns, $publicPatterns, collectionName } from '../user_pattern_utils.mjs'; import { useStore } from '@nanostores/react'; import { useMemo } from 'react'; import * as tunes from '../repl/tunes.mjs'; +export const stockPatterns = Object.fromEntries( + Object.entries(tunes).map(([key, code], i) => [i, { id: i, code, collection: collectionName.stock }]), +); + export const useExamplePatterns = () => { const featuredPatterns = useStore($featuredPatterns); const publicPatterns = useStore($publicPatterns); const collections = useMemo(() => { - const stockPatterns = Object.fromEntries(Object.entries(tunes).map(([key, code], i) => [i, { id: i, code }])); const pats = new Map(); - pats.set('Featured', featuredPatterns); - pats.set('Last Creations', publicPatterns); - pats.set('Stock Examples', stockPatterns); + pats.set(collectionName.featured, featuredPatterns); + pats.set(collectionName.public, publicPatterns); + pats.set(collectionName.stock, stockPatterns); return pats; }, [featuredPatterns, publicPatterns]); diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index 19afdabb..8bfcf8ce 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -4,11 +4,13 @@ import { getAudioContext, initializeAudioOutput, setDefaultAudioContext } from ' import { isTauri } from '../tauri.mjs'; import './Repl.css'; -import * as tunes from './tunes.mjs'; + import { createClient } from '@supabase/supabase-js'; import { nanoid } from 'nanoid'; import { writeText } from '@tauri-apps/api/clipboard'; import { createContext } from 'react'; +import { stockPatterns } from './useExamplePatterns'; +import { loadDBPatterns } from '@src/user_pattern_utils.mjs'; // Create a single supabase client for interacting with your database export const supabase = createClient( @@ -16,6 +18,10 @@ export const supabase = createClient( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', ); +if (typeof window !== 'undefined') { + loadDBPatterns(); +} + export async function initCode() { // load code from url hash (either short hash from database or decode long hash) try { @@ -47,10 +53,10 @@ export async function initCode() { } export function getRandomTune() { - const allTunes = Object.entries(tunes); + const allTunes = Object.entries(stockPatterns); const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; - const [name, code] = randomItem(allTunes); - return { name, code }; + const [id, data] = randomItem(allTunes); + return data; } export function loadModules() { diff --git a/website/src/user_pattern_utils.mjs b/website/src/user_pattern_utils.mjs index 7419a5a7..cddaa57c 100644 --- a/website/src/user_pattern_utils.mjs +++ b/website/src/user_pattern_utils.mjs @@ -9,21 +9,30 @@ import { supabase } from './repl/util.mjs'; export let $publicPatterns = atom([]); export let $featuredPatterns = atom([]); -const userPatternCollectionName = 'user'; + +export const collectionName = { + user: 'user', + public: 'Last Creations', + stock: 'Stock Examples', + featured: 'Featured', +}; + export let $viewingPatternData = persistentAtom( 'viewingPatternData', { id: '', code: '', - collection: userPatternCollectionName, + collection: collectionName.user, created_at: Date.now(), }, { listen: false }, ); export const getViewingPatternData = () => { - console.log(JSON.parse($viewingPatternData.get())); - return $viewingPatternData.get(); + return JSON.parse($viewingPatternData.get()); +}; +export const useViewingPatternData = () => { + return useStore($viewingPatternData); }; export const setViewingPatternData = (data) => { @@ -38,37 +47,34 @@ export function loadFeaturedPatterns() { return supabase.from('code').select().eq('featured', true).limit(20).order('id', { ascending: false }); } -async function loadDBPatterns() { +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); } catch (err) { - console.error('error loading patterns'); + console.error('error loading patterns', err); } } -if (typeof window !== 'undefined') { - loadDBPatterns(); -} - //pattern that the use is currently viewing in the window -const $viewingPattern = persistentAtom('viewingPattern', '', { listen: false }); -export function setViewingPattern(key) { - $viewingPattern.set(key); -} -export function getViewingPattern() { - return $viewingPattern.get(); -} +// const $viewingPattern = persistentAtom('viewingPattern', '', { listen: false }); +// export function setViewingPattern(key) { +// $viewingPattern.set(key); +// } +// export function getViewingPattern() { +// return $viewingPattern.get(); +// } -export function useViewingPattern() { - return useStore($viewingPattern); -} +// export function useViewingPattern() { +// return useStore($viewingPattern); +// } // active pattern is separate, because it shouldn't sync state across tabs // reason: https://github.com/tidalcycles/strudel/issues/857 const $activePattern = persistentAtom('activePattern', '', { listen: false }); @@ -83,20 +89,20 @@ export function useActivePattern() { return useStore($activePattern); } export function initUserCode(code) { - const patterns = { ...userPattern.getAll() }; - const match = Object.entries(patterns).find(([_, pat]) => pat.code === code); - const id = match?.[0]; - if (id != null) { - setActivePattern(id); - setViewingPattern(id); - } + // const patterns = { ...userPattern.getAll() }; + // const match = Object.entries(patterns).find(([_, pat]) => pat.code === code); + // const id = match?.[0]; + // if (id != null) { + // setActivePattern(id); + // setViewingPattern(id); + // } } export const setLatestCode = (code) => settingsMap.setKey('latestCode', code); const defaultCode = ''; export const userPattern = { - collection: userPatternCollectionName, + collection: collectionName.user, getAll() { const patterns = JSON.parse(settingsMap.get().userPatterns); return patterns ?? {}; @@ -108,6 +114,9 @@ export const userPattern = { exists(id) { return this.getPatternData(id) != null; }, + isValidID(id) { + return id != null && id.length > 0; + }, create() { const newID = createPatternID(); @@ -127,8 +136,8 @@ export const userPattern = { return { id, data }; }, duplicate(data) { - const newID = createPatternID(); - return this.update(newID, data); + const newPattern = this.create(); + return this.update(newPattern.id, { ...newPattern.data, code: data.code }); }, clearAll() { if (!confirm(`This will delete all your patterns. Are you really sure?`)) { @@ -150,11 +159,12 @@ export const userPattern = { setActivePattern(null); } setUserPatterns(userPatterns); - const viewingPattern = getViewingPattern(); - if (viewingPattern === id) { + const viewingPatternData = getViewingPatternData(); + const viewingID = viewingPatternData?.id; + if (viewingID === id) { return { id: null, data: { code: defaultCode } }; } - return { id: viewingPattern, data: userPatterns[viewingPattern] }; + return { id: viewingID, data: userPatterns[viewingID] }; }, }; @@ -166,10 +176,6 @@ export const createPatternID = () => { return nanoid(12); }; -export const getNextCloneID = (id) => { - return createPatternID(); -}; - export async function importPatterns(fileList) { const files = Array.from(fileList); await Promise.all( From 3431eaf30956768bf6d12b66d8a0a6842861b586 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Fri, 19 Jan 2024 00:20:10 -0500 Subject: [PATCH 17/31] cleaning up --- website/src/repl/Repl.jsx | 3 --- website/src/user_pattern_utils.mjs | 22 ---------------------- 2 files changed, 25 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index ce4561b8..8c22d05d 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -14,7 +14,6 @@ import { StrudelMirror, defaultSettings } from '@strudel/codemirror'; import { useCallback, useEffect, useRef, useState } from 'react'; import { settingsMap, useSettings } from '../settings.mjs'; import { - initUserCode, setActivePattern, setLatestCode, createPatternID, @@ -103,7 +102,6 @@ export function Repl({ embedded = false }) { let msg; if (decoded) { editor.setCode(decoded); - initUserCode(decoded); msg = `I have loaded the code from the URL.`; } else if (latestCode) { editor.setCode(latestCode); @@ -113,7 +111,6 @@ export function Repl({ embedded = false }) { msg = `A random code snippet named "${name}" has been loaded!`; } logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight'); - // setPending(false); }); editorRef.current = editor; diff --git a/website/src/user_pattern_utils.mjs b/website/src/user_pattern_utils.mjs index cddaa57c..19c1bdd3 100644 --- a/website/src/user_pattern_utils.mjs +++ b/website/src/user_pattern_utils.mjs @@ -63,19 +63,6 @@ export async function loadDBPatterns() { } } -//pattern that the use is currently viewing in the window -// const $viewingPattern = persistentAtom('viewingPattern', '', { listen: false }); -// export function setViewingPattern(key) { -// $viewingPattern.set(key); -// } -// export function getViewingPattern() { -// return $viewingPattern.get(); -// } - -// export function useViewingPattern() { -// return useStore($viewingPattern); -// } -// active pattern is separate, because it shouldn't sync state across tabs // reason: https://github.com/tidalcycles/strudel/issues/857 const $activePattern = persistentAtom('activePattern', '', { listen: false }); @@ -88,15 +75,6 @@ export function getActivePattern() { export function useActivePattern() { return useStore($activePattern); } -export function initUserCode(code) { - // const patterns = { ...userPattern.getAll() }; - // const match = Object.entries(patterns).find(([_, pat]) => pat.code === code); - // const id = match?.[0]; - // if (id != null) { - // setActivePattern(id); - // setViewingPattern(id); - // } -} export const setLatestCode = (code) => settingsMap.setKey('latestCode', code); From 7aea1b5fc853e9a03819be1dc7db28799a92078b Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Fri, 19 Jan 2024 00:27:09 -0500 Subject: [PATCH 18/31] fixed imports --- website/src/user_pattern_utils.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/src/user_pattern_utils.mjs b/website/src/user_pattern_utils.mjs index 19c1bdd3..7509e498 100644 --- a/website/src/user_pattern_utils.mjs +++ b/website/src/user_pattern_utils.mjs @@ -1,8 +1,7 @@ import { atom } from 'nanostores'; import { persistentAtom } from '@nanostores/persistent'; import { useStore } from '@nanostores/react'; - -import { logger } from '@strudel.cycles/core'; +import { logger } from '@strudel/core'; import { nanoid } from 'nanoid'; import { settingsMap } from './settings.mjs'; import { supabase } from './repl/util.mjs'; From af52ac30bae9ce98ab024ed69b3e1e669a2d49ae Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Fri, 19 Jan 2024 00:28:55 -0500 Subject: [PATCH 19/31] prettier --- website/src/settings.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/website/src/settings.mjs b/website/src/settings.mjs index be0e1104..788e991c 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -2,7 +2,6 @@ import { persistentMap } from '@nanostores/persistent'; import { useStore } from '@nanostores/react'; import { register } from '@strudel/core'; - export const defaultAudioDeviceName = 'System Standard'; export const defaultSettings = { From 0a349e1707b5263eeb7cb3f7b0bb3d70d467d8cc Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Fri, 19 Jan 2024 23:04:41 -0500 Subject: [PATCH 20/31] avoid json errors in firefox --- website/src/repl/panel/PatternsTab.jsx | 7 ++++--- website/src/repl/util.mjs | 9 +++++++++ website/src/user_pattern_utils.mjs | 8 ++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index 8c66d2ea..cce1275d 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -10,6 +10,7 @@ import { import { useMemo } from 'react'; import { getMetadata } from '../../metadata_parser'; import { useExamplePatterns } from '../useExamplePatterns'; +import { parseJSON } from '../util.mjs'; function classNames(...classes) { return classes.filter(Boolean).join(' '); @@ -42,7 +43,7 @@ function PatternButton({ showOutline, onClick, pattern, showHiglight }) { function PatternButtons({ patterns, activePattern, onClick, started }) { const viewingPatternStore = useViewingPatternData(); - const viewingPatternData = JSON.parse(viewingPatternStore); + const viewingPatternData = parseJSON(viewingPatternStore); const viewingPatternID = viewingPatternData.id; return (
@@ -76,7 +77,7 @@ function ActionButton({ children, onClick, label, labelIsHidden }) { export function PatternsTab({ context }) { const activePattern = useActivePattern(); const viewingPatternStore = useViewingPatternData(); - const viewingPatternData = JSON.parse(viewingPatternStore); + const viewingPatternData = parseJSON(viewingPatternStore); const { userPatterns } = useSettings(); const examplePatterns = useExamplePatterns(); @@ -164,7 +165,7 @@ export function PatternsTab({ context }) {

{collection}

updateCodeWindow({ ...patterns[id], collection }, true)} + onClick={(id) => updateCodeWindow({ ...patterns[id], collection }, false)} started={context.started} patterns={patterns} activePattern={activePattern} diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index 6d9e3087..65fdb4cf 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -52,6 +52,15 @@ export async function initCode() { } } +export const parseJSON = (json) => { + json = json != null && json.length ? json : '{}'; + try { + return JSON.parse(json); + } catch { + return '{}'; + } +}; + export function getRandomTune() { const allTunes = Object.entries(stockPatterns); const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; diff --git a/website/src/user_pattern_utils.mjs b/website/src/user_pattern_utils.mjs index 7509e498..6d5c4e3e 100644 --- a/website/src/user_pattern_utils.mjs +++ b/website/src/user_pattern_utils.mjs @@ -4,7 +4,7 @@ import { useStore } from '@nanostores/react'; import { logger } from '@strudel/core'; import { nanoid } from 'nanoid'; import { settingsMap } from './settings.mjs'; -import { supabase } from './repl/util.mjs'; +import { parseJSON, supabase } from './repl/util.mjs'; export let $publicPatterns = atom([]); export let $featuredPatterns = atom([]); @@ -28,7 +28,7 @@ export let $viewingPatternData = persistentAtom( ); export const getViewingPatternData = () => { - return JSON.parse($viewingPatternData.get()); + return parseJSON($viewingPatternData.get()); }; export const useViewingPatternData = () => { return useStore($viewingPatternData); @@ -81,7 +81,7 @@ const defaultCode = ''; export const userPattern = { collection: collectionName.user, getAll() { - const patterns = JSON.parse(settingsMap.get().userPatterns); + const patterns = parseJSON(settingsMap.get().userPatterns); return patterns ?? {}; }, getPatternData(id) { @@ -160,7 +160,7 @@ export async function importPatterns(fileList) { const content = await file.text(); if (file.type === 'application/json') { const userPatterns = userPattern.getAll(); - setUserPatterns({ ...userPatterns, ...JSON.parse(content) }); + setUserPatterns({ ...userPatterns, ...parseJSON(content) }); } else if (file.type === 'text/plain') { const id = file.name.replace(/\.[^/.]+$/, ''); userPattern.update(id, { code: content }); From b45fd5ce3a97fee9be5ffd998ed656e4033ce390 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 20 Jan 2024 13:00:33 -0500 Subject: [PATCH 21/31] fix invalid date --- website/src/repl/panel/PatternsTab.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index cce1275d..5cf9c8d6 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -21,7 +21,9 @@ function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) { return ( <>{`${pattern.id}: ${ - meta.title ?? pattern.hash ?? new Date(pattern.created_at).toLocaleDateString() ?? 'unnamed' + meta.title ?? pattern.hash ?? pattern.created_at != null + ? new Date(pattern.created_at).toLocaleDateString() + : 'unnamed' } by ${Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous'}`} ); } From 9dca7eb8785bd787b2b7aa23fdd1737417476c66 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 20 Jan 2024 13:59:32 -0500 Subject: [PATCH 22/31] add pattern filter behaves like sounds tab --- website/src/repl/panel/PatternsTab.jsx | 179 +++++++++++++----------- website/src/repl/useExamplePatterns.jsx | 4 +- website/src/settings.mjs | 2 + website/src/user_pattern_utils.mjs | 5 + 4 files changed, 104 insertions(+), 86 deletions(-) diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index 5cf9c8d6..083d93c4 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -1,8 +1,7 @@ -import { DocumentDuplicateIcon, TrashIcon } from '@heroicons/react/20/solid'; -import { useSettings } from '../../settings.mjs'; import { exportPatterns, importPatterns, + patternFilterName, useActivePattern, useViewingPatternData, userPattern, @@ -11,6 +10,8 @@ import { useMemo } from 'react'; import { getMetadata } from '../../metadata_parser'; import { useExamplePatterns } from '../useExamplePatterns'; import { parseJSON } from '../util.mjs'; +import { ButtonGroup } from './Forms.jsx'; +import { settingsMap, useSettings } from '../../settings.mjs'; function classNames(...classes) { return classes.filter(Boolean).join(' '); @@ -18,14 +19,22 @@ function classNames(...classes) { function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) { const meta = useMemo(() => getMetadata(pattern.code), [pattern]); + let title = meta.title; + if (title == null) { + title == pattern.hash; + } + if (title == null) { + const date = new Date(pattern.created_at); + if (isNaN(date)) { + return; + } + title = date.toLocaleDateString(); + } + if (title == null) { + title = 'unnamed'; + } - return ( - <>{`${pattern.id}: ${ - meta.title ?? pattern.hash ?? pattern.created_at != null - ? new Date(pattern.created_at).toLocaleDateString() - : 'unnamed' - } by ${Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous'}`} - ); + return <>{`${pattern.id}: ${title} by ${Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous'}`}; } function PatternButton({ showOutline, onClick, pattern, showHiglight }) { @@ -81,7 +90,7 @@ export function PatternsTab({ context }) { const viewingPatternStore = useViewingPatternData(); const viewingPatternData = parseJSON(viewingPatternStore); - const { userPatterns } = useSettings(); + const { userPatterns, patternFilter } = useSettings(); const examplePatterns = useExamplePatterns(); const collections = examplePatterns.collections; @@ -93,89 +102,91 @@ export function PatternsTab({ context }) { const isUserPattern = userPatterns[viewingPatternID] != null; return ( -
+
+ settingsMap.setKey('patternFilter', value)} + items={patternFilterName} + >
- {viewingIDIsValid && ( -
-

{`${viewingPatternID}`}

-
+ {patternFilter === patternFilterName.user && ( +
+
{ + const { data } = userPattern.createAndAddToDB(); + updateCodeWindow(data); + }} + /> + { const { data } = userPattern.duplicate(viewingPatternData); updateCodeWindow(data); }} - labelIsHidden - > - - - {isUserPattern && ( - { - const { data } = userPattern.delete(viewingPatternID); - updateCodeWindow({ ...data, collection: userPattern.collection }); - }} - labelIsHidden - > - - - )} -
-
- )} - updateCodeWindow({ ...userPatterns[id], collection: userPattern.collection }, false)} - patterns={userPatterns} - started={context.started} - activePattern={activePattern} - viewingPatternID={viewingPatternID} - /> -
- { - const { data } = userPattern.createAndAddToDB(); - updateCodeWindow(data); - }} - /> - { - const { data } = userPattern.clearAll(); - updateCodeWindow(data); - }} - /> + /> + { + const { data } = userPattern.delete(viewingPatternID); + updateCodeWindow({ ...data, collection: userPattern.collection }); + }} + /> + + - - -
-
- {Array.from(collections.keys()).map((collection) => { - const patterns = collections.get(collection); - return ( -
-

{collection}

-
- updateCodeWindow({ ...patterns[id], collection }, false)} - started={context.started} - patterns={patterns} - activePattern={activePattern} + { + const { data } = userPattern.clearAll(); + updateCodeWindow(data); + }} />
-
- ); - })} + {/* {viewingIDIsValid && ( +
+

{`${viewingPatternID}`}

+
+ )} */} + + updateCodeWindow({ ...userPatterns[id], collection: userPattern.collection }, false)} + patterns={userPatterns} + started={context.started} + activePattern={activePattern} + viewingPatternID={viewingPatternID} + /> +
+ )} + + {patternFilter !== patternFilterName.user && + Array.from(collections.keys()).map((collection) => { + const patterns = collections.get(collection); + return ( +
+

{collection}

+
+ updateCodeWindow({ ...patterns[id], collection }, false)} + started={context.started} + patterns={patterns} + activePattern={activePattern} + /> +
+
+ ); + })} +
); } diff --git a/website/src/repl/useExamplePatterns.jsx b/website/src/repl/useExamplePatterns.jsx index 92874db6..08629d44 100644 --- a/website/src/repl/useExamplePatterns.jsx +++ b/website/src/repl/useExamplePatterns.jsx @@ -4,7 +4,7 @@ import { useMemo } from 'react'; import * as tunes from '../repl/tunes.mjs'; export const stockPatterns = Object.fromEntries( - Object.entries(tunes).map(([key, code], i) => [i, { id: i, code, collection: collectionName.stock }]), + Object.entries(tunes).map(([key, code], i) => [i, { id: i, code, collection: 'Stock Examples' }]), ); export const useExamplePatterns = () => { @@ -14,7 +14,7 @@ export const useExamplePatterns = () => { const pats = new Map(); pats.set(collectionName.featured, featuredPatterns); pats.set(collectionName.public, publicPatterns); - pats.set(collectionName.stock, stockPatterns); + // pats.set(collectionName.stock, stockPatterns); return pats; }, [featuredPatterns, publicPatterns]); diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 788e991c..80d18ad7 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -1,6 +1,7 @@ import { persistentMap } from '@nanostores/persistent'; import { useStore } from '@nanostores/react'; import { register } from '@strudel/core'; +import { patternFilterName } from './user_pattern_utils.mjs'; export const defaultAudioDeviceName = 'System Standard'; @@ -20,6 +21,7 @@ export const defaultSettings = { latestCode: '', isZen: false, soundsFilter: 'all', + patternFilter: patternFilterName.community, panelPosition: 'right', userPatterns: '{}', audioDeviceName: defaultAudioDeviceName, diff --git a/website/src/user_pattern_utils.mjs b/website/src/user_pattern_utils.mjs index 6d5c4e3e..f9fef8d2 100644 --- a/website/src/user_pattern_utils.mjs +++ b/website/src/user_pattern_utils.mjs @@ -16,6 +16,11 @@ export const collectionName = { featured: 'Featured', }; +export const patternFilterName = { + community: 'community', + user: 'user', +}; + export let $viewingPatternData = persistentAtom( 'viewingPatternData', { From 3cd59cdea5ded9de628b8a557f51ea4780627d3b Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 20 Jan 2024 14:02:48 -0500 Subject: [PATCH 23/31] cleaning up --- website/src/repl/panel/PatternsTab.jsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index 083d93c4..5361f20f 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -153,12 +153,6 @@ export function PatternsTab({ context }) { }} />
- {/* {viewingIDIsValid && ( -
-

{`${viewingPatternID}`}

-
- )} */} - updateCodeWindow({ ...userPatterns[id], collection: userPattern.collection }, false)} patterns={userPatterns} From 4fe75143d022238500af319b3bd46a7a5515d1bc Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 20 Jan 2024 14:10:46 -0500 Subject: [PATCH 24/31] cleanup --- website/src/repl/panel/PatternsTab.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index 5361f20f..bcf4d541 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -98,8 +98,6 @@ export function PatternsTab({ context }) { context.handleUpdate(patternData, reset); }; const viewingPatternID = viewingPatternData?.id; - const viewingIDIsValid = userPattern.isValidID(viewingPatternID); - const isUserPattern = userPatterns[viewingPatternID] != null; return (
From 6c28398d20bac59f72d3b366b8faef849a014c86 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 20 Jan 2024 14:25:42 -0500 Subject: [PATCH 25/31] fix scroll --- website/src/repl/panel/PatternsTab.jsx | 106 ++++++++++++------------- 1 file changed, 52 insertions(+), 54 deletions(-) diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index bcf4d541..65597531 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -100,67 +100,65 @@ export function PatternsTab({ context }) { const viewingPatternID = viewingPatternData?.id; return ( -
+
settingsMap.setKey('patternFilter', value)} items={patternFilterName} > -
+
+ { + const { data } = userPattern.createAndAddToDB(); + updateCodeWindow(data); + }} + /> + { + const { data } = userPattern.duplicate(viewingPatternData); + updateCodeWindow(data); + }} + /> + { + const { data } = userPattern.delete(viewingPatternID); + updateCodeWindow({ ...data, collection: userPattern.collection }); + }} + /> + + + + { + const { data } = userPattern.clearAll(); + updateCodeWindow(data); + }} + /> +
+ +
{patternFilter === patternFilterName.user && ( -
-
- { - const { data } = userPattern.createAndAddToDB(); - updateCodeWindow(data); - }} - /> - { - const { data } = userPattern.duplicate(viewingPatternData); - updateCodeWindow(data); - }} - /> - { - const { data } = userPattern.delete(viewingPatternID); - updateCodeWindow({ ...data, collection: userPattern.collection }); - }} - /> - - - - { - const { data } = userPattern.clearAll(); - updateCodeWindow(data); - }} - /> -
- updateCodeWindow({ ...userPatterns[id], collection: userPattern.collection }, false)} - patterns={userPatterns} - started={context.started} - activePattern={activePattern} - viewingPatternID={viewingPatternID} - /> -
+ updateCodeWindow({ ...userPatterns[id], collection: userPattern.collection }, false)} + patterns={userPatterns} + started={context.started} + activePattern={activePattern} + viewingPatternID={viewingPatternID} + /> )} - {patternFilter !== patternFilterName.user && Array.from(collections.keys()).map((collection) => { const patterns = collections.get(collection); From 1146f6ba527252272084079acb02ef334fffccbd Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sat, 20 Jan 2024 18:14:42 -0500 Subject: [PATCH 26/31] fix annoying div --- website/src/repl/panel/PatternsTab.jsx | 80 +++++++++++++------------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/website/src/repl/panel/PatternsTab.jsx b/website/src/repl/panel/PatternsTab.jsx index 65597531..fb32eca6 100644 --- a/website/src/repl/panel/PatternsTab.jsx +++ b/website/src/repl/panel/PatternsTab.jsx @@ -106,47 +106,49 @@ export function PatternsTab({ context }) { onChange={(value) => settingsMap.setKey('patternFilter', value)} items={patternFilterName} > -
- { - const { data } = userPattern.createAndAddToDB(); - updateCodeWindow(data); - }} - /> - { - const { data } = userPattern.duplicate(viewingPatternData); - updateCodeWindow(data); - }} - /> - { - const { data } = userPattern.delete(viewingPatternID); - updateCodeWindow({ ...data, collection: userPattern.collection }); - }} - /> -