From a7713213f93736cd86d26480ef2a6185e4ebf0a3 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Sun, 10 Dec 2023 00:44:34 -0500 Subject: [PATCH] indexdb --- website/src/repl/ImportSoundsButton.jsx | 172 ++++++++++++++++++++---- 1 file changed, 143 insertions(+), 29 deletions(-) diff --git a/website/src/repl/ImportSoundsButton.jsx b/website/src/repl/ImportSoundsButton.jsx index 10f2ea48..502f25e4 100644 --- a/website/src/repl/ImportSoundsButton.jsx +++ b/website/src/repl/ImportSoundsButton.jsx @@ -1,38 +1,152 @@ -import React from 'react'; +import React, { useCallback, useEffect } from 'react'; +import { registerSound, onTriggerSample } from '@strudel.cycles/webaudio'; import { isAudioFile } from './files.mjs'; //choose a directory to locally import samples -export default function ImportSoundsButton({ onComplete }) { - let fileUploadRef = React.createRef(); - function mapFiles(soundFiles) { - const sounds = new Map(); - Array.from(soundFiles) - .sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' })) - .forEach((soundFile) => { - const name = soundFile.name; - if (!isAudioFile(name)) { +const userSamplesDB = 'testdb3'; +const sampleObject = 'testsamples'; + +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 registerSamples = () => { + openDB((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 = (onOpened) => { + if ('indexedDB' in window) { + // indexedDB supported + } else { + console.log('IndexedDB is not supported.'); + } + const dbOpen = indexedDB.open(userSamplesDB, 6); + + dbOpen.onupgradeneeded = (_event) => { + const db = dbOpen.result; + const objectStore = db.createObjectStore(sampleObject, { keyPath: 'id', autoIncrement: false }); + // objectStore.createIndex('name', 'name', { unique: false }); + objectStore.createIndex('blob', 'blob', { unique: false }); + objectStore.createIndex('title', 'title', { 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([sampleObject], 'readwrite'), + // get object store + objectStore = writeTransaction.objectStore(sampleObject); + // objectStore.put({ title: 'test', blob: 'test' }); + + 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; + } + // const id = crypto.randomUUID(); + const sUrl = URL.createObjectURL(s); + const buf = await fetch(sUrl).then((res) => res.arrayBuffer()); + const base64 = await bufferToDataUrl(buf); + const f = { + title, + blob: base64, + id: s.webkitRelativePath, + }; + return f; + }), + ).catch((error) => { + console.error(error); + }); +}; + +const setupUserSamplesDB = async (files, onComplete) => { + //clearData(); + await processFilesForIDB(files).then((files) => { + const onOpened = (objectStore, db) => { + files.forEach((file) => { + if (file == null) { return; } - const splitRelativePath = soundFile.webkitRelativePath?.split('/'); - const parentDirectory = splitRelativePath[splitRelativePath.length - 2]; - const soundPath = URL.createObjectURL(soundFile); - const soundPaths = sounds.get(parentDirectory) ?? new Set(); - soundPaths.add(soundPath); + const mutation = objectStore.put(file); + mutation.onsuccess = () => {}; + }); + onComplete(objectStore, db); + }; + openDB(onOpened); + }); +}; - 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, - }); +export default function ImportSoundsButton({ onComplete }) { + let fileUploadRef = React.createRef(); + const onChange = useCallback(async () => { + await setupUserSamplesDB(fileUploadRef.current.files, (objectStore, db) => {}).then(async () => { + registerSamples(); + onComplete(); }); - onComplete(); - } + }); + + useEffect(() => { + registerSamples(); + }, []); return (