From c4fe66421548bb56cfa3a4653400564b57815c75 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Sun, 10 Dec 2023 11:22:50 -0500 Subject: [PATCH] seperating things out --- website/src/repl/ImportSoundsButton.jsx | 160 ++---------------------- website/src/repl/Repl.jsx | 3 + website/src/repl/idbutils.mjs | 148 ++++++++++++++++++++++ 3 files changed, 164 insertions(+), 147 deletions(-) create mode 100644 website/src/repl/idbutils.mjs diff --git a/website/src/repl/ImportSoundsButton.jsx b/website/src/repl/ImportSoundsButton.jsx index 0cc1f90f..2ce208ae 100644 --- a/website/src/repl/ImportSoundsButton.jsx +++ b/website/src/repl/ImportSoundsButton.jsx @@ -1,164 +1,30 @@ -import React, { useCallback, useEffect } from 'react'; -import { registerSound, onTriggerSample } from '@strudel.cycles/webaudio'; -import { isAudioFile } from './files.mjs'; +import React, { useCallback, useState } from 'react'; +import { registerSamplesFromDB, uploadSamplesToDB, userSamplesDBConfig } from './idbutils.mjs'; //choose a directory to locally import samples - -const userSamplesDBConfig = { - dbName: 'samples', - table: 'usersamples', - columns: ['blob', 'title'], - version: 1, -}; - -function clearData() { - window.indexedDB - .databases() - .then((r) => { - for (var i = 0; i < r.length; i++) window.indexedDB.deleteDatabase(r[i].name); - }) - .then(() => { - alert('All data cleared.'); - }); -} - -const registerSamplesFromDB = (config) => { - openDB(config, (objectStore) => { - let query = objectStore.getAll(); - query.onsuccess = (event) => { - const soundFiles = event.target.result; - const sounds = new Map(); - [...soundFiles] - .sort((a, b) => a.title.localeCompare(b.title, undefined, { numeric: true, sensitivity: 'base' })) - .forEach((soundFile) => { - const title = soundFile.title; - if (!isAudioFile(title)) { - return; - } - const splitRelativePath = soundFile.id?.split('/'); - const parentDirectory = splitRelativePath[splitRelativePath.length - 2]; - const soundPath = soundFile.blob; - const soundPaths = sounds.get(parentDirectory) ?? new Set(); - soundPaths.add(soundPath); - sounds.set(parentDirectory, soundPaths); - }); - - sounds.forEach((soundPaths, key) => { - const value = Array.from(soundPaths); - registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), { - type: 'sample', - samples: value, - baseUrl: undefined, - prebake: false, - tag: undefined, - }); - }); - }; - }); -}; - -async function bufferToDataUrl(buf) { - return new Promise((resolve) => { - var blob = new Blob([buf], { type: 'application/octet-binary' }); - var reader = new FileReader(); - reader.onload = function (event) { - resolve(event.target.result); - }; - reader.readAsDataURL(blob); - }); -} - -const openDB = (config, onOpened) => { - const { dbName, version, table, columns } = config; - if (!('indexedDB' in window)) { - console.log('IndexedDB is not supported.'); - return; - } - const dbOpen = indexedDB.open(dbName, version); - - dbOpen.onupgradeneeded = (_event) => { - const db = dbOpen.result; - const objectStore = db.createObjectStore(table, { keyPath: 'id', autoIncrement: false }); - columns.forEach((c) => { - objectStore.createIndex(c, c, { unique: false }); - }); - }; - dbOpen.onerror = (err) => { - console.error(`indexedDB error: ${err.errorCode}`); - }; - - dbOpen.onsuccess = () => { - const db = dbOpen.result; - - const // lock store for writing - writeTransaction = db.transaction([table], 'readwrite'), - // get object store - objectStore = writeTransaction.objectStore(table); - onOpened(objectStore, db); - }; -}; - -const processFilesForIDB = async (files) => { - return await Promise.all( - Array.from(files) - .map(async (s) => { - const title = s.name; - if (!isAudioFile(title)) { - return; - } - //create obscured url to file system that can be fetched - const sUrl = URL.createObjectURL(s); - //fetch the sound and turn it into a buffer array - const buf = await fetch(sUrl).then((res) => res.arrayBuffer()); - //create a url blob containing all of the buffer data - const base64 = await bufferToDataUrl(buf); - return { - title, - blob: base64, - id: s.webkitRelativePath, - }; - }) - .filter(Boolean), - ).catch((error) => { - console.error(error); - }); -}; - -const uploadSamplesToDB = async (config, files) => { - // clearData(); - await processFilesForIDB(files).then((files) => { - const onOpened = (objectStore, _db) => { - files.forEach((file) => { - if (file == null) { - return; - } - const mutation = objectStore.put(file); - mutation.onsuccess = () => {}; - }); - }; - openDB(config, onOpened); - }); -}; - export default function ImportSoundsButton({ onComplete }) { let fileUploadRef = React.createRef(); + const [isUploading, setIsUploading] = useState(false); const onChange = useCallback(async () => { + if (!fileUploadRef.current.files?.length) { + return; + } + setIsUploading(true); await uploadSamplesToDB(userSamplesDBConfig, fileUploadRef.current.files).then(() => { - registerSamplesFromDB(userSamplesDBConfig); - onComplete(); + registerSamplesFromDB(userSamplesDBConfig, () => { + onComplete(); + setIsUploading(false); + }); }); }); - useEffect(() => { - registerSamplesFromDB(userSamplesDBConfig); - }, []); - return ( ); } diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 2700bcf9..10f1cdda 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -24,6 +24,7 @@ import { code2hash, hash2code } from './helpers.mjs'; import { isTauri } from '../tauri.mjs'; import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs'; import { writeText } from '@tauri-apps/api/clipboard'; +import { registerSamplesFromDB, userSamplesDBConfig } from './idbutils.mjs'; const { latestCode } = settingsMap.get(); @@ -183,6 +184,8 @@ export function Repl({ embedded = false }) { setCode(randomTune); msg = `A random code snippet named "${name}" has been loaded!`; } + //registers samples that have been saved to the index DB + registerSamplesFromDB(userSamplesDBConfig); logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight'); setPending(false); }); diff --git a/website/src/repl/idbutils.mjs b/website/src/repl/idbutils.mjs new file mode 100644 index 00000000..414006b9 --- /dev/null +++ b/website/src/repl/idbutils.mjs @@ -0,0 +1,148 @@ +import { registerSound, onTriggerSample } from '@strudel.cycles/webaudio'; +import { isAudioFile } from './files.mjs'; +import { logger } from '@strudel.cycles/core'; + +//utilites for writing and reading to the indexdb + +export const userSamplesDBConfig = { + dbName: 'samples', + table: 'usersamples', + columns: ['blob', 'title'], + version: 1, +}; + +// deletes all of the databases, useful for debugging +const clearIDB = () => { + window.indexedDB + .databases() + .then((r) => { + for (var i = 0; i < r.length; i++) window.indexedDB.deleteDatabase(r[i].name); + }) + .then(() => { + alert('All data cleared.'); + }); +}; + +// queries the DB, and registers the sounds so they can be played +export const registerSamplesFromDB = (config, onComplete = () => {}) => { + openDB(config, (objectStore) => { + let query = objectStore.getAll(); + query.onsuccess = (event) => { + const soundFiles = event.target.result; + if (!soundFiles?.length) { + return; + } + const sounds = new Map(); + [...soundFiles] + .sort((a, b) => a.title.localeCompare(b.title, undefined, { numeric: true, sensitivity: 'base' })) + .forEach((soundFile) => { + const title = soundFile.title; + if (!isAudioFile(title)) { + return; + } + const splitRelativePath = soundFile.id?.split('/'); + const parentDirectory = splitRelativePath[splitRelativePath.length - 2]; + const soundPath = soundFile.blob; + const soundPaths = sounds.get(parentDirectory) ?? new Set(); + soundPaths.add(soundPath); + sounds.set(parentDirectory, soundPaths); + }); + + sounds.forEach((soundPaths, key) => { + const value = Array.from(soundPaths); + registerSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), { + type: 'sample', + samples: value, + baseUrl: undefined, + prebake: false, + tag: undefined, + }); + }); + logger('imported sounds registered!', 'success'); + onComplete(); + }; + }); +}; +// creates a blob from a buffer that can be read +async function bufferToDataUrl(buf) { + return new Promise((resolve) => { + var blob = new Blob([buf], { type: 'application/octet-binary' }); + var reader = new FileReader(); + reader.onload = function (event) { + resolve(event.target.result); + }; + reader.readAsDataURL(blob); + }); +} +//open db and initialize it if necessary +const openDB = (config, onOpened) => { + const { dbName, version, table, columns } = config; + if (!('indexedDB' in window)) { + console.log('IndexedDB is not supported.'); + return; + } + const dbOpen = indexedDB.open(dbName, version); + + dbOpen.onupgradeneeded = (_event) => { + const db = dbOpen.result; + const objectStore = db.createObjectStore(table, { keyPath: 'id', autoIncrement: false }); + columns.forEach((c) => { + objectStore.createIndex(c, c, { unique: false }); + }); + }; + dbOpen.onerror = (err) => { + logger('Something went wrong while trying to open the the client DB', 'error'); + console.error(`indexedDB error: ${err.errorCode}`); + }; + + dbOpen.onsuccess = () => { + const db = dbOpen.result; + + const // lock store for writing + writeTransaction = db.transaction([table], 'readwrite'), + // get object store + objectStore = writeTransaction.objectStore(table); + onOpened(objectStore, db); + }; +}; + +const processFilesForIDB = async (files) => { + return await Promise.all( + Array.from(files) + .map(async (s) => { + const title = s.name; + if (!isAudioFile(title)) { + return; + } + //create obscured url to file system that can be fetched + const sUrl = URL.createObjectURL(s); + //fetch the sound and turn it into a buffer array + const buf = await fetch(sUrl).then((res) => res.arrayBuffer()); + //create a url blob containing all of the buffer data + const base64 = await bufferToDataUrl(buf); + return { + title, + blob: base64, + id: s.webkitRelativePath, + }; + }) + .filter(Boolean), + ).catch((error) => { + logger('Something went wrong while processing uploaded files', 'error'); + console.error(error); + }); +}; + +export const uploadSamplesToDB = async (config, files) => { + await processFilesForIDB(files).then((files) => { + const onOpened = (objectStore, _db) => { + files.forEach((file) => { + if (file == null) { + return; + } + objectStore.put(file); + }); + }; + openDB(config, onOpened); + }); +};