Merge branch 'main' into jade/uwu2000

This commit is contained in:
Jade (Rose) Rowland 2025-02-24 20:12:12 -05:00
commit d9c3f051b9
17 changed files with 528 additions and 141 deletions

View file

@ -1,6 +1,8 @@
import {
exportPatterns,
importPatterns,
loadAndSetFeaturedPatterns,
loadAndSetPublicPatterns,
patternFilterName,
useActivePattern,
useViewingPatternData,
@ -12,10 +14,10 @@ import { useExamplePatterns } from '../../useExamplePatterns.jsx';
import { parseJSON, isUdels } from '../../util.mjs';
import { ButtonGroup } from './Forms.jsx';
import { settingsMap, useSettings } from '../../../settings.mjs';
function classNames(...classes) {
return classes.filter(Boolean).join(' ');
}
import { Pagination } from '../pagination/Pagination.jsx';
import { useState } from 'react';
import { useDebounce } from '../usedebounce.jsx';
import cx from '@src/cx.mjs';
export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
const meta = useMemo(() => getMetadata(pattern.code), [pattern]);
@ -33,13 +35,15 @@ export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
if (title == null) {
title = 'unnamed';
}
return <>{`${pattern.id}: ${title} by ${Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous'}`}</>;
const author = Array.isArray(meta.by) ? meta.by.join(',') : 'Anonymous';
return <>{`${pattern.id}: ${title} by ${author.slice(0, 100)}`.slice(0, 60)}</>;
}
function PatternButton({ showOutline, onClick, pattern, showHiglight }) {
return (
<a
className={classNames(
className={cx(
'mr-4 hover:opacity-50 cursor-pointer block',
showOutline && 'outline outline-1',
showHiglight && 'bg-selection',
@ -84,82 +88,72 @@ function ActionButton({ children, onClick, label, labelIsHidden }) {
);
}
export function PatternsTab({ context }) {
const updateCodeWindow = (context, patternData, reset = false) => {
context.handleUpdate(patternData, reset);
};
const autoResetPatternOnChange = !isUdels();
function UserPatterns({ context }) {
const activePattern = useActivePattern();
const viewingPatternStore = useViewingPatternData();
const viewingPatternData = parseJSON(viewingPatternStore);
const { userPatterns, patternFilter } = useSettings();
const examplePatterns = useExamplePatterns();
const collections = examplePatterns.collections;
const updateCodeWindow = (patternData, reset = false) => {
context.handleUpdate(patternData, reset);
};
const viewingPatternID = viewingPatternData?.id;
const autoResetPatternOnChange = !isUdels();
return (
<div className="px-4 w-full text-foreground space-y-2 flex flex-col overflow-hidden max-h-full h-full">
<ButtonGroup
value={patternFilter}
onChange={(value) => settingsMap.setKey('patternFilter', value)}
items={patternFilterName}
></ButtonGroup>
{patternFilter === patternFilterName.user && (
<div>
<div className="pr-4 space-x-4 border-b border-foreground flex max-w-full overflow-x-auto">
<ActionButton
label="new"
onClick={() => {
const { data } = userPattern.createAndAddToDB();
updateCodeWindow(data);
}}
/>
<ActionButton
label="duplicate"
onClick={() => {
const { data } = userPattern.duplicate(viewingPatternData);
updateCodeWindow(data);
}}
/>
<ActionButton
label="delete"
onClick={() => {
const { data } = userPattern.delete(viewingPatternID);
updateCodeWindow({ ...data, collection: userPattern.collection });
}}
/>
<label className="hover:opacity-50 cursor-pointer">
<input
style={{ display: 'none' }}
type="file"
multiple
accept="text/plain,application/json"
onChange={(e) => importPatterns(e.target.files)}
/>
import
</label>
<ActionButton label="export" onClick={exportPatterns} />
<div className="flex flex-col gap-2 flex-grow overflow-hidden h-full pb-2 ">
<div className="pr-4 space-x-4 flex max-w-full overflow-x-auto">
<ActionButton
label="new"
onClick={() => {
const { data } = userPattern.createAndAddToDB();
updateCodeWindow(context, data);
}}
/>
<ActionButton
label="duplicate"
onClick={() => {
const { data } = userPattern.duplicate(viewingPatternData);
updateCodeWindow(context, data);
}}
/>
<ActionButton
label="delete"
onClick={() => {
const { data } = userPattern.delete(viewingPatternID);
updateCodeWindow(context, { ...data, collection: userPattern.collection });
}}
/>
<label className="hover:opacity-50 cursor-pointer">
<input
style={{ display: 'none' }}
type="file"
multiple
accept="text/plain,application/json"
onChange={(e) => importPatterns(e.target.files)}
/>
import
</label>
<ActionButton label="export" onClick={exportPatterns} />
<ActionButton
label="delete-all"
onClick={() => {
const { data } = userPattern.clearAll();
updateCodeWindow(data);
}}
/>
</div>
</div>
)}
<ActionButton
label="delete-all"
onClick={() => {
const { data } = userPattern.clearAll();
updateCodeWindow(context, data);
}}
/>
</div>
<section className="flex overflow-y-auto max-h-full flex-grow flex-col">
<div className="overflow-auto h-full bg-background p-2 rounded-md">
{patternFilter === patternFilterName.user && (
<PatternButtons
onClick={(id) =>
updateCodeWindow({ ...userPatterns[id], collection: userPattern.collection }, autoResetPatternOnChange)
updateCodeWindow(
context,
{ ...userPatterns[id], collection: userPattern.collection },
autoResetPatternOnChange,
)
}
patterns={userPatterns}
started={context.started}
@ -167,23 +161,111 @@ export function PatternsTab({ context }) {
viewingPatternID={viewingPatternID}
/>
)}
{patternFilter !== patternFilterName.user &&
Array.from(collections.keys()).map((collection) => {
const patterns = collections.get(collection);
return (
<section key={collection} className="py-2">
<h2 className="text-xl mb-2">{collection}</h2>
<PatternButtons
onClick={(id) => updateCodeWindow({ ...patterns[id], collection }, autoResetPatternOnChange)}
started={context.started}
patterns={patterns}
activePattern={activePattern}
/>
</section>
);
})}
</section>
</div>
</div>
);
}
function PatternPageWithPagination({ patterns, patternOnClick, context, paginationOnChange, initialPage }) {
const [page, setPage] = useState(initialPage);
const debouncedPageChange = useDebounce(() => {
paginationOnChange(page);
});
const onPageChange = (pageNum) => {
setPage(pageNum);
debouncedPageChange();
};
const activePattern = useActivePattern();
return (
<div className="flex flex-grow flex-col h-full overflow-hidden justify-between">
<div className="overflow-auto flex flex-col flex-grow bg-background p-2 rounded-md ">
<PatternButtons
onClick={(id) => patternOnClick(id)}
started={context.started}
patterns={patterns}
activePattern={activePattern}
/>
</div>
<div className="flex items-center gap-2 py-2">
<label htmlFor="pattern pagination">Page</label>
<Pagination id="pattern pagination" currPage={page} onPageChange={onPageChange} />
</div>
</div>
);
}
let featuredPageNum = 1;
function FeaturedPatterns({ context }) {
const examplePatterns = useExamplePatterns();
const collections = examplePatterns.collections;
const patterns = collections.get(patternFilterName.featured);
return (
<PatternPageWithPagination
patterns={patterns}
context={context}
initialPage={featuredPageNum}
patternOnClick={(id) => {
updateCodeWindow(
context,
{ ...patterns[id], collection: patternFilterName.featured },
autoResetPatternOnChange,
);
}}
paginationOnChange={async (pageNum) => {
await loadAndSetFeaturedPatterns(pageNum - 1);
featuredPageNum = pageNum;
}}
/>
);
}
let latestPageNum = 1;
function LatestPatterns({ context }) {
const examplePatterns = useExamplePatterns();
const collections = examplePatterns.collections;
const patterns = collections.get(patternFilterName.public);
return (
<PatternPageWithPagination
patterns={patterns}
context={context}
initialPage={latestPageNum}
patternOnClick={(id) => {
updateCodeWindow(context, { ...patterns[id], collection: patternFilterName.public }, autoResetPatternOnChange);
}}
paginationOnChange={async (pageNum) => {
await loadAndSetPublicPatterns(pageNum - 1);
latestPageNum = pageNum;
}}
/>
);
}
function PublicPatterns({ context }) {
const { patternFilter } = useSettings();
if (patternFilter === patternFilterName.featured) {
return <FeaturedPatterns context={context} />;
}
return <LatestPatterns context={context} />;
}
export function PatternsTab({ context }) {
const { patternFilter } = useSettings();
return (
<div className="px-4 w-full text-foreground space-y-2 flex flex-col overflow-hidden max-h-full h-full">
<ButtonGroup
value={patternFilter}
onChange={(value) => settingsMap.setKey('patternFilter', value)}
items={patternFilterName}
></ButtonGroup>
{patternFilter === patternFilterName.user ? (
<UserPatterns context={context} />
) : (
<PublicPatterns context={context} />
)}
</div>
);
}

View file

@ -1,7 +1,7 @@
import { useMemo, useState } from 'react';
import jsdocJson from '../../../../../doc.json';
import { Textbox } from '../textbox/Textbox';
const availableFunctions = jsdocJson.docs
.filter(({ name, description }) => name && !name.startsWith('_') && !!description)
.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name));
@ -29,12 +29,7 @@ export function Reference() {
<div className="flex h-full w-full p-2 overflow-hidden">
<div className="h-full flex flex-col gap-2 w-1/3 max-w-72 ">
<div class="w-full flex">
<input
className="w-full p-1 bg-background rounded-md border-none"
placeholder="Search"
value={search}
onInput={(event) => setSearch(event.target.value)}
/>
<Textbox className="w-full" placeholder="Search" value={search} onChange={setSearch} />
</div>
<div className="flex flex-col h-full overflow-y-auto gap-1.5 bg-background bg-opacity-50 rounded-md">
{visibleFunctions.map((entry, i) => (

View file

@ -5,6 +5,7 @@ import { useMemo, useRef, useState } from 'react';
import { settingsMap, useSettings } from '../../../settings.mjs';
import { ButtonGroup } from './Forms.jsx';
import ImportSoundsButton from './ImportSoundsButton.jsx';
import { Textbox } from '../textbox/Textbox.jsx';
const getSamples = (samples) =>
Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1;
@ -52,13 +53,8 @@ export function SoundsTab() {
});
return (
<div id="sounds-tab" className="px-4 flex flex-col w-full h-full text-foreground">
<input
className="w-full p-1 bg-background rounded-md my-2"
placeholder="Search"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<div id="sounds-tab" className="px-4 flex flex-col w-full h-full">
<Textbox placeholder="Search" value={search} onChange={(v) => setSearch(v)} />
<div className="pb-2 flex shrink-0 flex-wrap">
<ButtonGroup

View file

@ -46,7 +46,8 @@ export function WelcomeTab({ context }) {
<a href="https://github.com/tidalcycles/strudel" target="_blank">
github
</a>
. Please consider to{' '}
. You can also find <a href="https://github.com/felixroos/dough-samples/blob/main/README.md">licensing info</a>{' '}
for the default sound banks there. Please consider to{' '}
<a href="https://opencollective.com/tidalcycles" target="_blank">
support this project
</a>{' '}