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(