mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-11 05:38:34 +00:00
Merge pull request #1287 from daslyfe/jade/supabasequery
Create Pattern Page Pagination
This commit is contained in:
commit
1f233b9e7d
67
website/src/repl/components/incrementor/Incrementor.jsx
Normal file
67
website/src/repl/components/incrementor/Incrementor.jsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Textbox } from '../textbox/Textbox';
|
||||||
|
import cx from '@src/cx.mjs';
|
||||||
|
|
||||||
|
function IncButton({ children, className, ...buttonProps }) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
tabIndex={-1}
|
||||||
|
className={cx(
|
||||||
|
'border border-transparent p-1 text-center text-sm transition-all hover:bg-foreground active:bg-lineBackground disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
type="button"
|
||||||
|
{...buttonProps}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function Incrementor({
|
||||||
|
onChange,
|
||||||
|
value,
|
||||||
|
min = -Infinity,
|
||||||
|
max = Infinity,
|
||||||
|
className,
|
||||||
|
incrementLabel = 'next page',
|
||||||
|
decrementLabel = 'prev page',
|
||||||
|
...incrementorProps
|
||||||
|
}) {
|
||||||
|
value = parseInt(value);
|
||||||
|
value = isNaN(value) ? '' : value;
|
||||||
|
return (
|
||||||
|
<div className={cx('w-fit bg-background relative flex items-center"> rounded-md', className)}>
|
||||||
|
<Textbox
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
onChange={(v) => {
|
||||||
|
if (v.length && v < min) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onChange(v);
|
||||||
|
}}
|
||||||
|
type="number"
|
||||||
|
placeholder=""
|
||||||
|
value={value}
|
||||||
|
className="w-32 mb-0 mt-0 border-none rounded-r-none bg-transparent appearance-none [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
|
||||||
|
{...incrementorProps}
|
||||||
|
/>
|
||||||
|
<div className="flex gap-1 ">
|
||||||
|
<IncButton disabled={value <= min} onClick={() => onChange(value - 1)} aria-label={decrementLabel}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" className="w-4 h-4">
|
||||||
|
<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
|
||||||
|
</svg>
|
||||||
|
</IncButton>
|
||||||
|
<IncButton
|
||||||
|
className="rounded-r-md"
|
||||||
|
disabled={value >= max}
|
||||||
|
onClick={() => onChange(value + 1)}
|
||||||
|
aria-label={incrementLabel}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" className="w-4 h-4">
|
||||||
|
<path d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z" />
|
||||||
|
</svg>
|
||||||
|
</IncButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
5
website/src/repl/components/pagination/Pagination.jsx
Normal file
5
website/src/repl/components/pagination/Pagination.jsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Incrementor } from '../incrementor/Incrementor';
|
||||||
|
|
||||||
|
export function Pagination({ currPage, onPageChange, className, ...incrementorProps }) {
|
||||||
|
return <Incrementor min={1} value={currPage} onChange={onPageChange} className={className} {...incrementorProps} />;
|
||||||
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
exportPatterns,
|
exportPatterns,
|
||||||
importPatterns,
|
importPatterns,
|
||||||
|
loadAndSetFeaturedPatterns,
|
||||||
|
loadAndSetPublicPatterns,
|
||||||
patternFilterName,
|
patternFilterName,
|
||||||
useActivePattern,
|
useActivePattern,
|
||||||
useViewingPatternData,
|
useViewingPatternData,
|
||||||
@ -12,10 +14,10 @@ import { useExamplePatterns } from '../../useExamplePatterns.jsx';
|
|||||||
import { parseJSON, isUdels } from '../../util.mjs';
|
import { parseJSON, isUdels } from '../../util.mjs';
|
||||||
import { ButtonGroup } from './Forms.jsx';
|
import { ButtonGroup } from './Forms.jsx';
|
||||||
import { settingsMap, useSettings } from '../../../settings.mjs';
|
import { settingsMap, useSettings } from '../../../settings.mjs';
|
||||||
|
import { Pagination } from '../pagination/Pagination.jsx';
|
||||||
function classNames(...classes) {
|
import { useState } from 'react';
|
||||||
return classes.filter(Boolean).join(' ');
|
import { useDebounce } from '../usedebounce.jsx';
|
||||||
}
|
import cx from '@src/cx.mjs';
|
||||||
|
|
||||||
export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
|
export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
|
||||||
const meta = useMemo(() => getMetadata(pattern.code), [pattern]);
|
const meta = useMemo(() => getMetadata(pattern.code), [pattern]);
|
||||||
@ -33,13 +35,15 @@ export function PatternLabel({ pattern } /* : { pattern: Tables<'code'> } */) {
|
|||||||
if (title == null) {
|
if (title == null) {
|
||||||
title = 'unnamed';
|
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 }) {
|
function PatternButton({ showOutline, onClick, pattern, showHiglight }) {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
className={classNames(
|
className={cx(
|
||||||
'mr-4 hover:opacity-50 cursor-pointer block',
|
'mr-4 hover:opacity-50 cursor-pointer block',
|
||||||
showOutline && 'outline outline-1',
|
showOutline && 'outline outline-1',
|
||||||
showHiglight && 'bg-selection',
|
showHiglight && 'bg-selection',
|
||||||
@ -57,7 +61,7 @@ function PatternButtons({ patterns, activePattern, onClick, started }) {
|
|||||||
const viewingPatternID = viewingPatternData.id;
|
const viewingPatternID = viewingPatternData.id;
|
||||||
return (
|
return (
|
||||||
<div className="font-mono text-sm">
|
<div className="font-mono text-sm">
|
||||||
{Object.values(patterns)
|
{Object.values(patterns ?? {})
|
||||||
.reverse()
|
.reverse()
|
||||||
.map((pattern) => {
|
.map((pattern) => {
|
||||||
const id = pattern.id;
|
const id = pattern.id;
|
||||||
@ -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 activePattern = useActivePattern();
|
||||||
const viewingPatternStore = useViewingPatternData();
|
const viewingPatternStore = useViewingPatternData();
|
||||||
const viewingPatternData = parseJSON(viewingPatternStore);
|
const viewingPatternData = parseJSON(viewingPatternStore);
|
||||||
|
|
||||||
const { userPatterns, patternFilter } = useSettings();
|
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 viewingPatternID = viewingPatternData?.id;
|
||||||
|
|
||||||
const autoResetPatternOnChange = !isUdels();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-4 w-full dark:text-white text-stone-900 space-y-2 flex flex-col overflow-hidden max-h-full h-full">
|
<div className="flex flex-col gap-2 flex-grow overflow-hidden h-full pb-2 ">
|
||||||
<ButtonGroup
|
<div className="pr-4 space-x-4 flex max-w-full overflow-x-auto">
|
||||||
value={patternFilter}
|
<ActionButton
|
||||||
onChange={(value) => settingsMap.setKey('patternFilter', value)}
|
label="new"
|
||||||
items={patternFilterName}
|
onClick={() => {
|
||||||
></ButtonGroup>
|
const { data } = userPattern.createAndAddToDB();
|
||||||
{patternFilter === patternFilterName.user && (
|
updateCodeWindow(context, data);
|
||||||
<div>
|
}}
|
||||||
<div className="pr-4 space-x-4 border-b border-foreground flex max-w-full overflow-x-auto">
|
/>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
label="new"
|
label="duplicate"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const { data } = userPattern.createAndAddToDB();
|
const { data } = userPattern.duplicate(viewingPatternData);
|
||||||
updateCodeWindow(data);
|
updateCodeWindow(context, data);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
label="duplicate"
|
label="delete"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const { data } = userPattern.duplicate(viewingPatternData);
|
const { data } = userPattern.delete(viewingPatternID);
|
||||||
updateCodeWindow(data);
|
updateCodeWindow(context, { ...data, collection: userPattern.collection });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ActionButton
|
<label className="hover:opacity-50 cursor-pointer">
|
||||||
label="delete"
|
<input
|
||||||
onClick={() => {
|
style={{ display: 'none' }}
|
||||||
const { data } = userPattern.delete(viewingPatternID);
|
type="file"
|
||||||
updateCodeWindow({ ...data, collection: userPattern.collection });
|
multiple
|
||||||
}}
|
accept="text/plain,application/json"
|
||||||
/>
|
onChange={(e) => importPatterns(e.target.files)}
|
||||||
<label className="hover:opacity-50 cursor-pointer">
|
/>
|
||||||
<input
|
import
|
||||||
style={{ display: 'none' }}
|
</label>
|
||||||
type="file"
|
<ActionButton label="export" onClick={exportPatterns} />
|
||||||
multiple
|
|
||||||
accept="text/plain,application/json"
|
|
||||||
onChange={(e) => importPatterns(e.target.files)}
|
|
||||||
/>
|
|
||||||
import
|
|
||||||
</label>
|
|
||||||
<ActionButton label="export" onClick={exportPatterns} />
|
|
||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
label="delete-all"
|
label="delete-all"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const { data } = userPattern.clearAll();
|
const { data } = userPattern.clearAll();
|
||||||
updateCodeWindow(data);
|
updateCodeWindow(context, data);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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 && (
|
{patternFilter === patternFilterName.user && (
|
||||||
<PatternButtons
|
<PatternButtons
|
||||||
onClick={(id) =>
|
onClick={(id) =>
|
||||||
updateCodeWindow({ ...userPatterns[id], collection: userPattern.collection }, autoResetPatternOnChange)
|
updateCodeWindow(
|
||||||
|
context,
|
||||||
|
{ ...userPatterns[id], collection: userPattern.collection },
|
||||||
|
autoResetPatternOnChange,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
patterns={userPatterns}
|
patterns={userPatterns}
|
||||||
started={context.started}
|
started={context.started}
|
||||||
@ -167,24 +161,111 @@ export function PatternsTab({ context }) {
|
|||||||
viewingPatternID={viewingPatternID}
|
viewingPatternID={viewingPatternID}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{patternFilter !== patternFilterName.user &&
|
</div>
|
||||||
Array.from(collections.keys()).map((collection) => {
|
</div>
|
||||||
const patterns = collections.get(collection);
|
);
|
||||||
return (
|
}
|
||||||
<section key={collection} className="py-2">
|
|
||||||
<h2 className="text-xl mb-2">{collection}</h2>
|
function PatternPageWithPagination({ patterns, patternOnClick, context, paginationOnChange, initialPage }) {
|
||||||
<div className="font-mono text-sm">
|
const [page, setPage] = useState(initialPage);
|
||||||
<PatternButtons
|
const debouncedPageChange = useDebounce(() => {
|
||||||
onClick={(id) => updateCodeWindow({ ...patterns[id], collection }, autoResetPatternOnChange)}
|
paginationOnChange(page);
|
||||||
started={context.started}
|
});
|
||||||
patterns={patterns}
|
|
||||||
activePattern={activePattern}
|
const onPageChange = (pageNum) => {
|
||||||
/>
|
setPage(pageNum);
|
||||||
</div>
|
debouncedPageChange();
|
||||||
</section>
|
};
|
||||||
);
|
|
||||||
})}
|
const activePattern = useActivePattern();
|
||||||
</section>
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|
||||||
import jsdocJson from '../../../../../doc.json';
|
import jsdocJson from '../../../../../doc.json';
|
||||||
|
import { Textbox } from '../textbox/Textbox';
|
||||||
const availableFunctions = jsdocJson.docs
|
const availableFunctions = jsdocJson.docs
|
||||||
.filter(({ name, description }) => name && !name.startsWith('_') && !!description)
|
.filter(({ name, description }) => name && !name.startsWith('_') && !!description)
|
||||||
.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name));
|
.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name));
|
||||||
@ -28,12 +29,7 @@ export function Reference() {
|
|||||||
<div className="flex h-full w-full p-2 text-foreground overflow-hidden">
|
<div className="flex h-full w-full p-2 text-foreground overflow-hidden">
|
||||||
<div className="h-full flex flex-col gap-2 w-1/3 max-w-72 ">
|
<div className="h-full flex flex-col gap-2 w-1/3 max-w-72 ">
|
||||||
<div class="w-full flex">
|
<div class="w-full flex">
|
||||||
<input
|
<Textbox className="w-full" placeholder="Search" value={search} onChange={setSearch} />
|
||||||
className="w-full p-1 bg-background rounded-md border-none"
|
|
||||||
placeholder="Search"
|
|
||||||
value={search}
|
|
||||||
onInput={(event) => setSearch(event.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col h-full overflow-y-auto gap-1.5 bg-background bg-opacity-50 rounded-md">
|
<div className="flex flex-col h-full overflow-y-auto gap-1.5 bg-background bg-opacity-50 rounded-md">
|
||||||
{visibleFunctions.map((entry, i) => (
|
{visibleFunctions.map((entry, i) => (
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { useMemo, useRef, useState } from 'react';
|
|||||||
import { settingsMap, useSettings } from '../../../settings.mjs';
|
import { settingsMap, useSettings } from '../../../settings.mjs';
|
||||||
import { ButtonGroup } from './Forms.jsx';
|
import { ButtonGroup } from './Forms.jsx';
|
||||||
import ImportSoundsButton from './ImportSoundsButton.jsx';
|
import ImportSoundsButton from './ImportSoundsButton.jsx';
|
||||||
|
import { Textbox } from '../textbox/Textbox.jsx';
|
||||||
|
|
||||||
const getSamples = (samples) =>
|
const getSamples = (samples) =>
|
||||||
Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1;
|
Array.isArray(samples) ? samples.length : typeof samples === 'object' ? Object.values(samples).length : 1;
|
||||||
@ -53,12 +54,7 @@ export function SoundsTab() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="sounds-tab" className="px-4 flex flex-col w-full h-full dark:text-white text-stone-900">
|
<div id="sounds-tab" className="px-4 flex flex-col w-full h-full dark:text-white text-stone-900">
|
||||||
<input
|
<Textbox placeholder="Search" value={search} onChange={(v) => setSearch(v)} />
|
||||||
className="w-full p-1 bg-background rounded-md my-2"
|
|
||||||
placeholder="Search"
|
|
||||||
value={search}
|
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="pb-2 flex shrink-0 flex-wrap">
|
<div className="pb-2 flex shrink-0 flex-wrap">
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
|
|||||||
11
website/src/repl/components/textbox/Textbox.jsx
Normal file
11
website/src/repl/components/textbox/Textbox.jsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import cx from '@src/cx.mjs';
|
||||||
|
|
||||||
|
export function Textbox({ onChange, className, ...inputProps }) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className={cx('p-1 bg-background rounded-md my-2 border-foreground', className)}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
{...inputProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
30
website/src/repl/components/usedebounce.jsx
Normal file
30
website/src/repl/components/usedebounce.jsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
|
||||||
|
function debounce(fn, wait) {
|
||||||
|
let timer;
|
||||||
|
return function (...args) {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
timer = setTimeout(() => fn(...args), wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDebounce(callback) {
|
||||||
|
const ref = useRef;
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current = callback;
|
||||||
|
}, [callback]);
|
||||||
|
|
||||||
|
const debouncedCallback = useMemo(() => {
|
||||||
|
const func = () => {
|
||||||
|
ref.current?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
return debounce(func, 1000);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return debouncedCallback;
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { $featuredPatterns, $publicPatterns, collectionName } from '../user_pattern_utils.mjs';
|
import { $featuredPatterns, $publicPatterns, patternFilterName } 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';
|
||||||
@ -12,9 +12,9 @@ export const useExamplePatterns = () => {
|
|||||||
const publicPatterns = useStore($publicPatterns);
|
const publicPatterns = useStore($publicPatterns);
|
||||||
const collections = useMemo(() => {
|
const collections = useMemo(() => {
|
||||||
const pats = new Map();
|
const pats = new Map();
|
||||||
pats.set(collectionName.featured, featuredPatterns);
|
pats.set(patternFilterName.featured, featuredPatterns);
|
||||||
pats.set(collectionName.public, publicPatterns);
|
pats.set(patternFilterName.public, publicPatterns);
|
||||||
// pats.set(collectionName.stock, stockPatterns);
|
// pats.set(patternFilterName.stock, stockPatterns);
|
||||||
return pats;
|
return pats;
|
||||||
}, [featuredPatterns, publicPatterns]);
|
}, [featuredPatterns, publicPatterns]);
|
||||||
|
|
||||||
|
|||||||
@ -8,16 +8,12 @@ import { confirmDialog, parseJSON, supabase } from './repl/util.mjs';
|
|||||||
export let $publicPatterns = atom([]);
|
export let $publicPatterns = atom([]);
|
||||||
export let $featuredPatterns = atom([]);
|
export let $featuredPatterns = atom([]);
|
||||||
|
|
||||||
export const collectionName = {
|
const patternQueryLimit = 20;
|
||||||
user: 'user',
|
|
||||||
public: 'Last Creations',
|
|
||||||
stock: 'Stock Examples',
|
|
||||||
featured: 'Featured',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const patternFilterName = {
|
export const patternFilterName = {
|
||||||
community: 'community',
|
public: 'latest',
|
||||||
|
featured: 'featured',
|
||||||
user: 'user',
|
user: 'user',
|
||||||
|
// stock: 'stock examples',
|
||||||
};
|
};
|
||||||
|
|
||||||
const sessionAtom = (name, initial = undefined) => {
|
const sessionAtom = (name, initial = undefined) => {
|
||||||
@ -36,7 +32,7 @@ const sessionAtom = (name, initial = undefined) => {
|
|||||||
export let $viewingPatternData = sessionAtom('viewingPatternData', {
|
export let $viewingPatternData = sessionAtom('viewingPatternData', {
|
||||||
id: '',
|
id: '',
|
||||||
code: '',
|
code: '',
|
||||||
collection: collectionName.user,
|
collection: patternFilterName.user,
|
||||||
created_at: Date.now(),
|
created_at: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -51,25 +47,50 @@ export const setViewingPatternData = (data) => {
|
|||||||
$viewingPatternData.set(JSON.stringify(data));
|
$viewingPatternData.set(JSON.stringify(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
export function loadPublicPatterns() {
|
function parsePageNum(page) {
|
||||||
return supabase.from('code_v1').select().eq('public', true).limit(20).order('id', { ascending: false });
|
return isNaN(page) ? 0 : page;
|
||||||
|
}
|
||||||
|
export function loadPublicPatterns(page) {
|
||||||
|
page = parsePageNum(page);
|
||||||
|
const offset = page * patternQueryLimit;
|
||||||
|
return supabase
|
||||||
|
.from('code_v1')
|
||||||
|
.select()
|
||||||
|
.eq('public', true)
|
||||||
|
.range(offset, offset + patternQueryLimit)
|
||||||
|
.order('id', { ascending: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadFeaturedPatterns() {
|
export function loadFeaturedPatterns(page = 0) {
|
||||||
return supabase.from('code_v1').select().eq('featured', true).limit(20).order('id', { ascending: false });
|
page = parsePageNum(page);
|
||||||
|
const offset = page * patternQueryLimit;
|
||||||
|
return supabase
|
||||||
|
.from('code_v1')
|
||||||
|
.select()
|
||||||
|
.eq('featured', true)
|
||||||
|
.range(offset, offset + patternQueryLimit)
|
||||||
|
.order('id', { ascending: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadAndSetPublicPatterns(page) {
|
||||||
|
const p = await loadPublicPatterns(page);
|
||||||
|
const data = p?.data;
|
||||||
|
const pats = {};
|
||||||
|
data?.forEach((data, key) => (pats[data.id ?? key] = data));
|
||||||
|
$publicPatterns.set(pats);
|
||||||
|
}
|
||||||
|
export async function loadAndSetFeaturedPatterns(page) {
|
||||||
|
const p = await loadFeaturedPatterns(page);
|
||||||
|
const data = p?.data;
|
||||||
|
const pats = {};
|
||||||
|
data?.forEach((data, key) => (pats[data.id ?? key] = data));
|
||||||
|
$featuredPatterns.set(pats);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadDBPatterns() {
|
export async function loadDBPatterns() {
|
||||||
try {
|
try {
|
||||||
const { data: publicPatterns } = await loadPublicPatterns();
|
await loadAndSetPublicPatterns();
|
||||||
const { data: featuredPatterns } = await loadFeaturedPatterns();
|
await loadAndSetFeaturedPatterns();
|
||||||
const featured = {};
|
|
||||||
const pub = {};
|
|
||||||
|
|
||||||
publicPatterns?.forEach((data, key) => (pub[data.id ?? key] = data));
|
|
||||||
featuredPatterns?.forEach((data, key) => (featured[data.id ?? key] = data));
|
|
||||||
$publicPatterns.set(pub);
|
|
||||||
$featuredPatterns.set(featured);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('error loading patterns', err);
|
console.error('error loading patterns', err);
|
||||||
}
|
}
|
||||||
@ -92,7 +113,7 @@ export const setLatestCode = (code) => settingsMap.setKey('latestCode', code);
|
|||||||
|
|
||||||
const defaultCode = '';
|
const defaultCode = '';
|
||||||
export const userPattern = {
|
export const userPattern = {
|
||||||
collection: collectionName.user,
|
collection: patternFilterName.user,
|
||||||
getAll() {
|
getAll() {
|
||||||
const patterns = parseJSON(settingsMap.get().userPatterns);
|
const patterns = parseJSON(settingsMap.get().userPatterns);
|
||||||
return patterns ?? {};
|
return patterns ?? {};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user