Merge branch 'main' into audio_device_selection

This commit is contained in:
Jade (Rose) Rowland 2023-12-18 00:22:33 -05:00 • committed by GitHub
commit 355ac2ea29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
45 changed files with 2510 additions and 295 deletions

View file

@ -9,7 +9,7 @@ export function ButtonGroup({ value, onChange, items }) {
key={key}
onClick={() => onChange(key)}
className={cx(
'px-2 border-b h-8',
'px-2 border-b h-8 whitespace-nowrap',
// i === 0 && 'rounded-l-md',
// i === arr.length - 1 && 'rounded-r-md',
// value === key ? 'bg-background' : 'bg-lineHighlight',

View file

@ -0,0 +1,43 @@
import React, { useCallback, useState } from 'react';
import { registerSamplesFromDB, uploadSamplesToDB, userSamplesDBConfig } from '../idbutils.mjs';
//choose a directory to locally import samples
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();
setIsUploading(false);
});
});
});
return (
<label
style={{ alignItems: 'center' }}
className="flex bg-background ml-2 pl-2 pr-2 max-w-[300px] rounded-md hover:opacity-50 whitespace-nowrap cursor-pointer"
>
<input
disabled={isUploading}
ref={fileUploadRef}
id="audio_file"
style={{ display: 'none' }}
type="file"
directory=""
webkitdirectory=""
multiple
accept="audio/*"
onChange={() => {
onChange();
}}
/>
{isUploading ? 'importing...' : 'import sounds'}
</label>
);
}

View file

@ -1,27 +1,27 @@
import { DocumentDuplicateIcon, PencilSquareIcon, TrashIcon } from '@heroicons/react/20/solid';
import { useMemo } from 'react';
import * as tunes from '../tunes.mjs';
import {
useSettings,
clearUserPatterns,
newUserPattern,
setActivePattern,
deleteActivePattern,
duplicateActivePattern,
exportPatterns,
getUserPattern,
getUserPatterns,
importPatterns,
newUserPattern,
renameActivePattern,
addUserPattern,
setUserPatterns,
setActivePattern,
useActivePattern,
useSettings,
} from '../../settings.mjs';
import { logger } from '@strudel.cycles/core';
import { DocumentDuplicateIcon, PencilSquareIcon, TrashIcon } from '@heroicons/react/20/solid';
import * as tunes from '../tunes.mjs';
function classNames(...classes) {
return classes.filter(Boolean).join(' ');
}
export function PatternsTab({ context }) {
const { userPatterns, activePattern } = useSettings();
const { userPatterns } = useSettings();
const activePattern = useActivePattern();
const isExample = useMemo(() => activePattern && !!tunes[activePattern], [activePattern]);
return (
<div className="px-4 w-full dark:text-white text-stone-900 space-y-4 pb-4">
@ -85,38 +85,11 @@ export function PatternsTab({ context }) {
type="file"
multiple
accept="text/plain,application/json"
onChange={async (e) => {
const files = Array.from(e.target.files);
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 name = file.name.replace(/\.[^/.]+$/, '');
addUserPattern(name, { code: content });
}
}),
);
logger(`import done!`);
}}
onChange={(e) => importPatterns(e.target.files)}
/>
import
</label>
<button
className="hover:opacity-50"
onClick={() => {
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);
}}
>
<button className="hover:opacity-50" onClick={() => exportPatterns()}>
export
</button>
</div>

View file

@ -5,6 +5,7 @@ import { getAudioContext, soundMap, connectToDestination } from '@strudel.cycles
import React, { useMemo, useRef } from 'react';
import { settingsMap, useSettings } from '../../settings.mjs';
import { ButtonGroup } from './Forms.jsx';
import ImportSoundsButton from './ImportSoundsButton.jsx';
const getSamples = (samples) =>
Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1;
@ -43,7 +44,7 @@ export function SoundsTab() {
});
return (
<div id="sounds-tab" className="px-4 flex flex-col w-full h-full dark:text-white text-stone-900">
<div className="pb-2 flex-none">
<div className="pb-2 flex shrink-0 overflow-auto">
<ButtonGroup
value={soundsFilter}
onChange={(value) => settingsMap.setKey('soundsFilter', value)}
@ -54,6 +55,7 @@ export function SoundsTab() {
user: 'User',
}}
></ButtonGroup>
<ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} />
</div>
<div className="min-h-0 max-h-full grow overflow-auto font-mono text-sm break-normal">
{soundEntries.map(([name, { data, onTrigger }]) => (