cleaning up

This commit is contained in:
Jade (Rose) Rowland 2024-01-19 00:14:08 -05:00
parent 77a48e8351
commit 9a13c58583
5 changed files with 113 additions and 95 deletions

View file

@ -17,7 +17,6 @@ import {
initUserCode, initUserCode,
setActivePattern, setActivePattern,
setLatestCode, setLatestCode,
setViewingPattern,
createPatternID, createPatternID,
userPattern, userPattern,
getViewingPatternData, getViewingPatternData,
@ -77,7 +76,6 @@ export function Repl({ embedded = false }) {
setLatestCode(code); setLatestCode(code);
window.location.hash = '#' + code2hash(code); window.location.hash = '#' + code2hash(code);
const viewingPatternData = getViewingPatternData(); const viewingPatternData = getViewingPatternData();
const data = { ...viewingPatternData, code }; const data = { ...viewingPatternData, code };
let id = data.id; let id = data.id;
const isExamplePattern = viewingPatternData.collection !== userPattern.collection; const isExamplePattern = viewingPatternData.collection !== userPattern.collection;
@ -86,13 +84,12 @@ export function Repl({ embedded = false }) {
const codeHasChanged = code !== viewingPatternData.code; const codeHasChanged = code !== viewingPatternData.code;
if (codeHasChanged) { if (codeHasChanged) {
// fork example // fork example
id = createPatternID(); const newPattern = userPattern.duplicate(data);
setViewingPattern(id); id = newPattern.id;
setViewingPatternData(userPattern.update(id, data).data); setViewingPatternData(newPattern.data);
} }
} else { } else {
id = id == null ? createPatternID() : id; id = userPattern.isValidID(id) ? id : createPatternID();
setViewingPattern(id);
setViewingPatternData(userPattern.update(id, data).data); setViewingPatternData(userPattern.update(id, data).data);
} }
setActivePattern(id); setActivePattern(id);
@ -169,23 +166,23 @@ export function Repl({ embedded = false }) {
await prebake(); // declare default samples await prebake(); // declare default samples
}; };
const handleUpdate = async (id, data, reset = false) => { const handleUpdate = async (patternData, reset = false) => {
setViewingPatternData(data);
if (reset) { if (reset) {
await resetEditor(); await resetEditor();
} }
setViewingPattern(id); setViewingPatternData(patternData);
editorRef.current.setCode(data.code); editorRef.current.setCode(patternData.code);
}; };
const handleEvaluate = () => { const handleEvaluate = () => {
editorRef.current.evaluate(); editorRef.current.evaluate();
}; };
const handleShuffle = async () => { const handleShuffle = async () => {
const { code, name } = getRandomTune(); const patternData = getRandomTune();
logger(`[repl] ✨ loading random tune "${name}"`); const code = patternData.code;
setActivePattern(name); logger(`[repl] ✨ loading random tune "${patternData.id}"`);
setViewingPattern(name); setActivePattern(patternData.id);
setViewingPatternData(patternData);
clearCanvas(); clearCanvas();
resetLoadedSounds(); resetLoadedSounds();
await prebake(); // declare default samples await prebake(); // declare default samples

View file

@ -4,7 +4,7 @@ import {
exportPatterns, exportPatterns,
importPatterns, importPatterns,
useActivePattern, useActivePattern,
useViewingPattern, useViewingPatternData,
userPattern, userPattern,
} from '../../user_pattern_utils.mjs'; } from '../../user_pattern_utils.mjs';
import { useMemo } from 'react'; 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 ( return (
<div className="font-mono text-sm"> <div className="font-mono text-sm">
{Object.values(patterns).map((pattern) => { {Object.values(patterns)
const id = pattern.id; .reverse()
return ( .map((pattern) => {
<PatternButton const id = pattern.id;
pattern={pattern} return (
key={id} <PatternButton
showHiglight={id === viewingPattern} pattern={pattern}
showOutline={id === activePattern && started} key={id}
onClick={() => onClick(id)} showHiglight={id === viewingPatternID}
/> showOutline={id === activePattern && started}
); onClick={() => onClick(id)}
})} />
);
})}
</div> </div>
); );
} }
@ -70,30 +75,32 @@ function ActionButton({ children, onClick, label, labelIsHidden }) {
export function PatternsTab({ context }) { export function PatternsTab({ context }) {
const activePattern = useActivePattern(); const activePattern = useActivePattern();
const viewingPattern = useViewingPattern(); const viewingPatternStore = useViewingPatternData();
const viewingPatternData = JSON.parse(viewingPatternStore);
const { userPatterns } = useSettings(); const { userPatterns } = useSettings();
const examplePatterns = useExamplePatterns(); const examplePatterns = useExamplePatterns();
const collections = examplePatterns.collections; const collections = examplePatterns.collections;
const updateCodeWindow = (id, data, reset = false) => { const updateCodeWindow = (patternData, reset = false) => {
context.handleUpdate(id, data, reset); context.handleUpdate(patternData, reset);
}; };
const isUserPattern = userPatterns[viewingPattern] != null; const viewingPatternID = viewingPatternData?.id;
const viewingIDIsValid = userPattern.isValidID(viewingPatternID);
const isUserPattern = userPatterns[viewingPatternID] != null;
return ( return (
<div className="px-4 w-full dark:text-white text-stone-900 space-y-4 pb-4"> <div className="px-4 w-full dark:text-white text-stone-900 space-y-4 pb-4">
<section> <section>
{viewingPattern && ( {viewingIDIsValid && (
<div className="flex items-center mb-2 space-x-2 overflow-auto"> <div className="flex items-center mb-2 space-x-2 overflow-auto">
<h1 className="text-xl">{`${viewingPattern}`}</h1> <h1 className="text-xl">{`${viewingPatternID}`}</h1>
<div className="space-x-4 flex w-min"> <div className="space-x-4 flex w-min">
<ActionButton <ActionButton
label="Duplicate" label="Duplicate"
onClick={() => { onClick={() => {
const { id, data } = userPattern.duplicate( const { data } = userPattern.duplicate(viewingPatternData);
userPattern.getPatternData(id) ?? examplePatterns.patterns[id], updateCodeWindow(data);
);
updateCodeWindow(id, data);
}} }}
labelIsHidden labelIsHidden
> >
@ -103,8 +110,8 @@ export function PatternsTab({ context }) {
<ActionButton <ActionButton
label="Delete" label="Delete"
onClick={() => { onClick={() => {
const { id, data } = userPattern.delete(viewingPattern); const { data } = userPattern.delete(viewingPatternID);
updateCodeWindow(id, { ...data, collection: userPattern.collection }); updateCodeWindow({ ...data, collection: userPattern.collection });
}} }}
labelIsHidden labelIsHidden
> >
@ -115,25 +122,25 @@ export function PatternsTab({ context }) {
</div> </div>
)} )}
<PatternButtons <PatternButtons
onClick={(id) => updateCodeWindow(id, { ...userPatterns[id], collection: userPattern.collection }, false)} onClick={(id) => updateCodeWindow({ ...userPatterns[id], collection: userPattern.collection }, false)}
patterns={userPatterns} patterns={userPatterns}
started={context.started} started={context.started}
activePattern={activePattern} activePattern={activePattern}
viewingPattern={viewingPattern} viewingPatternID={viewingPatternID}
/> />
<div className="pr-4 space-x-4 border-b border-foreground mb-2 h-8 flex overflow-auto max-w-full items-center"> <div className="pr-4 space-x-4 border-b border-foreground mb-2 h-8 flex overflow-auto max-w-full items-center">
<ActionButton <ActionButton
label="new" label="new"
onClick={() => { onClick={() => {
const { id, data } = userPattern.createAndAddToDB(); const { data } = userPattern.createAndAddToDB();
updateCodeWindow(id, data); updateCodeWindow(data);
}} }}
/> />
<ActionButton <ActionButton
label="clear" label="clear"
onClick={() => { onClick={() => {
const { id, data } = userPattern.clearAll(); const { data } = userPattern.clearAll();
updateCodeWindow(id, data); updateCodeWindow(data);
}} }}
/> />
@ -157,11 +164,10 @@ export function PatternsTab({ context }) {
<h2 className="text-xl mb-2">{collection}</h2> <h2 className="text-xl mb-2">{collection}</h2>
<div className="font-mono text-sm"> <div className="font-mono text-sm">
<PatternButtons <PatternButtons
onClick={(id) => updateCodeWindow(id, { ...patterns[id], collection }, true)} onClick={(id) => updateCodeWindow({ ...patterns[id], collection }, true)}
started={context.started} started={context.started}
patterns={patterns} patterns={patterns}
activePattern={activePattern} activePattern={activePattern}
viewingPattern={viewingPattern}
/> />
</div> </div>
</section> </section>

View file

@ -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 { useStore } from '@nanostores/react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import * as tunes from '../repl/tunes.mjs'; 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 = () => { export const useExamplePatterns = () => {
const featuredPatterns = useStore($featuredPatterns); const featuredPatterns = useStore($featuredPatterns);
const publicPatterns = useStore($publicPatterns); const publicPatterns = useStore($publicPatterns);
const collections = useMemo(() => { const collections = useMemo(() => {
const stockPatterns = Object.fromEntries(Object.entries(tunes).map(([key, code], i) => [i, { id: i, code }]));
const pats = new Map(); const pats = new Map();
pats.set('Featured', featuredPatterns); pats.set(collectionName.featured, featuredPatterns);
pats.set('Last Creations', publicPatterns); pats.set(collectionName.public, publicPatterns);
pats.set('Stock Examples', stockPatterns); pats.set(collectionName.stock, stockPatterns);
return pats; return pats;
}, [featuredPatterns, publicPatterns]); }, [featuredPatterns, publicPatterns]);

View file

@ -4,11 +4,13 @@ import { getAudioContext, initializeAudioOutput, setDefaultAudioContext } from '
import { isTauri } from '../tauri.mjs'; import { isTauri } from '../tauri.mjs';
import './Repl.css'; import './Repl.css';
import * as tunes from './tunes.mjs';
import { createClient } from '@supabase/supabase-js'; import { createClient } from '@supabase/supabase-js';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { writeText } from '@tauri-apps/api/clipboard'; import { writeText } from '@tauri-apps/api/clipboard';
import { createContext } from 'react'; 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 // Create a single supabase client for interacting with your database
export const supabase = createClient( export const supabase = createClient(
@ -16,6 +18,10 @@ export const supabase = createClient(
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM',
); );
if (typeof window !== 'undefined') {
loadDBPatterns();
}
export async function initCode() { export async function initCode() {
// load code from url hash (either short hash from database or decode long hash) // load code from url hash (either short hash from database or decode long hash)
try { try {
@ -47,10 +53,10 @@ export async function initCode() {
} }
export function getRandomTune() { export function getRandomTune() {
const allTunes = Object.entries(tunes); const allTunes = Object.entries(stockPatterns);
const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
const [name, code] = randomItem(allTunes); const [id, data] = randomItem(allTunes);
return { name, code }; return data;
} }
export function loadModules() { export function loadModules() {

View file

@ -9,21 +9,30 @@ import { supabase } from './repl/util.mjs';
export let $publicPatterns = atom([]); export let $publicPatterns = atom([]);
export let $featuredPatterns = 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( export let $viewingPatternData = persistentAtom(
'viewingPatternData', 'viewingPatternData',
{ {
id: '', id: '',
code: '', code: '',
collection: userPatternCollectionName, collection: collectionName.user,
created_at: Date.now(), created_at: Date.now(),
}, },
{ listen: false }, { listen: false },
); );
export const getViewingPatternData = () => { export const getViewingPatternData = () => {
console.log(JSON.parse($viewingPatternData.get())); return JSON.parse($viewingPatternData.get());
return $viewingPatternData.get(); };
export const useViewingPatternData = () => {
return useStore($viewingPatternData);
}; };
export const setViewingPatternData = (data) => { 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 }); return supabase.from('code').select().eq('featured', true).limit(20).order('id', { ascending: false });
} }
async function loadDBPatterns() { export async function loadDBPatterns() {
try { try {
const { data: publicPatterns } = await loadPublicPatterns(); const { data: publicPatterns } = await loadPublicPatterns();
const { data: featuredPatterns } = await loadFeaturedPatterns(); const { data: featuredPatterns } = await loadFeaturedPatterns();
const featured = {}; const featured = {};
const pub = {}; const pub = {};
publicPatterns?.forEach((data, key) => (pub[data.id ?? key] = data)); publicPatterns?.forEach((data, key) => (pub[data.id ?? key] = data));
featuredPatterns?.forEach((data, key) => (featured[data.id ?? key] = data)); featuredPatterns?.forEach((data, key) => (featured[data.id ?? key] = data));
$publicPatterns.set(pub); $publicPatterns.set(pub);
$featuredPatterns.set(featured); $featuredPatterns.set(featured);
} catch (err) { } 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 //pattern that the use is currently viewing in the window
const $viewingPattern = persistentAtom('viewingPattern', '', { listen: false }); // const $viewingPattern = persistentAtom('viewingPattern', '', { listen: false });
export function setViewingPattern(key) { // export function setViewingPattern(key) {
$viewingPattern.set(key); // $viewingPattern.set(key);
} // }
export function getViewingPattern() { // export function getViewingPattern() {
return $viewingPattern.get(); // return $viewingPattern.get();
} // }
export function useViewingPattern() { // export function useViewingPattern() {
return useStore($viewingPattern); // return useStore($viewingPattern);
} // }
// active pattern is separate, because it shouldn't sync state across tabs // active pattern is separate, because it shouldn't sync state across tabs
// reason: https://github.com/tidalcycles/strudel/issues/857 // reason: https://github.com/tidalcycles/strudel/issues/857
const $activePattern = persistentAtom('activePattern', '', { listen: false }); const $activePattern = persistentAtom('activePattern', '', { listen: false });
@ -83,20 +89,20 @@ export function useActivePattern() {
return useStore($activePattern); return useStore($activePattern);
} }
export function initUserCode(code) { export function initUserCode(code) {
const patterns = { ...userPattern.getAll() }; // const patterns = { ...userPattern.getAll() };
const match = Object.entries(patterns).find(([_, pat]) => pat.code === code); // const match = Object.entries(patterns).find(([_, pat]) => pat.code === code);
const id = match?.[0]; // const id = match?.[0];
if (id != null) { // if (id != null) {
setActivePattern(id); // setActivePattern(id);
setViewingPattern(id); // setViewingPattern(id);
} // }
} }
export const setLatestCode = (code) => settingsMap.setKey('latestCode', code); export const setLatestCode = (code) => settingsMap.setKey('latestCode', code);
const defaultCode = ''; const defaultCode = '';
export const userPattern = { export const userPattern = {
collection: userPatternCollectionName, collection: collectionName.user,
getAll() { getAll() {
const patterns = JSON.parse(settingsMap.get().userPatterns); const patterns = JSON.parse(settingsMap.get().userPatterns);
return patterns ?? {}; return patterns ?? {};
@ -108,6 +114,9 @@ export const userPattern = {
exists(id) { exists(id) {
return this.getPatternData(id) != null; return this.getPatternData(id) != null;
}, },
isValidID(id) {
return id != null && id.length > 0;
},
create() { create() {
const newID = createPatternID(); const newID = createPatternID();
@ -127,8 +136,8 @@ export const userPattern = {
return { id, data }; return { id, data };
}, },
duplicate(data) { duplicate(data) {
const newID = createPatternID(); const newPattern = this.create();
return this.update(newID, data); return this.update(newPattern.id, { ...newPattern.data, code: data.code });
}, },
clearAll() { clearAll() {
if (!confirm(`This will delete all your patterns. Are you really sure?`)) { if (!confirm(`This will delete all your patterns. Are you really sure?`)) {
@ -150,11 +159,12 @@ export const userPattern = {
setActivePattern(null); setActivePattern(null);
} }
setUserPatterns(userPatterns); setUserPatterns(userPatterns);
const viewingPattern = getViewingPattern(); const viewingPatternData = getViewingPatternData();
if (viewingPattern === id) { const viewingID = viewingPatternData?.id;
if (viewingID === id) {
return { id: null, data: { code: defaultCode } }; 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); return nanoid(12);
}; };
export const getNextCloneID = (id) => {
return createPatternID();
};
export async function importPatterns(fileList) { export async function importPatterns(fileList) {
const files = Array.from(fileList); const files = Array.from(fileList);
await Promise.all( await Promise.all(