mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-27 05:28:41 +00:00
Merge pull request #1193 from daslyfe/orgin/daslyfe/feat/pinmenu
Menu Panel Improvements!
This commit is contained in:
commit
835c7b6879
@ -1,5 +1,5 @@
|
|||||||
import Loader from '@src/repl/components/Loader';
|
import Loader from '@src/repl/components/Loader';
|
||||||
import { Panel } from '@src/repl/components/panel/Panel';
|
import { HorizontalPanel } from '@src/repl/components/panel/Panel';
|
||||||
import { Code } from '@src/repl/components/Code';
|
import { Code } from '@src/repl/components/Code';
|
||||||
import BigPlayButton from '@src/repl/components/BigPlayButton';
|
import BigPlayButton from '@src/repl/components/BigPlayButton';
|
||||||
import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage';
|
import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage';
|
||||||
@ -20,7 +20,7 @@ export default function UdelsEditor(Props) {
|
|||||||
<Code containerRef={containerRef} editorRef={editorRef} init={init} />
|
<Code containerRef={containerRef} editorRef={editorRef} init={init} />
|
||||||
</div>
|
</div>
|
||||||
<UserFacingErrorMessage error={error} />
|
<UserFacingErrorMessage error={error} />
|
||||||
<Panel context={context} />
|
<HorizontalPanel context={context} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
import { silence, noteToMidi, _mod } from '@strudel/core';
|
import { silence, noteToMidi, _mod } from '@strudel/core';
|
||||||
import { clearHydra } from '@strudel/hydra';
|
|
||||||
import { getDrawContext, getPunchcardPainter } from '@strudel/draw';
|
import { getDrawContext, getPunchcardPainter } from '@strudel/draw';
|
||||||
import { transpiler } from '@strudel/transpiler';
|
import { transpiler } from '@strudel/transpiler';
|
||||||
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
|
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import Loader from '@src/repl/components/Loader';
|
import Loader from '@src/repl/components/Loader';
|
||||||
import { Panel } from '@src/repl/components/panel/Panel';
|
import { HorizontalPanel, VerticalPanel } from '@src/repl/components/panel/Panel';
|
||||||
import { Code } from '@src/repl/components/Code';
|
import { Code } from '@src/repl/components/Code';
|
||||||
import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage';
|
import UserFacingErrorMessage from '@src/repl/components/UserFacingErrorMessage';
|
||||||
import { Header } from './Header';
|
import { Header } from './Header';
|
||||||
@ -21,10 +21,10 @@ export default function ReplEditor(Props) {
|
|||||||
<Header context={context} />
|
<Header context={context} />
|
||||||
<div className="grow flex relative overflow-hidden">
|
<div className="grow flex relative overflow-hidden">
|
||||||
<Code containerRef={containerRef} editorRef={editorRef} init={init} />
|
<Code containerRef={containerRef} editorRef={editorRef} init={init} />
|
||||||
{panelPosition === 'right' && <Panel context={context} />}
|
{panelPosition === 'right' && <VerticalPanel context={context} />}
|
||||||
</div>
|
</div>
|
||||||
<UserFacingErrorMessage error={error} />
|
<UserFacingErrorMessage error={error} />
|
||||||
{panelPosition === 'bottom' && <Panel context={context} />}
|
{panelPosition === 'bottom' && <HorizontalPanel context={context} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import cx from '@src/cx.mjs';
|
|||||||
|
|
||||||
export function ConsoleTab({ log }) {
|
export function ConsoleTab({ log }) {
|
||||||
return (
|
return (
|
||||||
<div id="console-tab" className="break-all px-4 dark:text-white text-stone-900 text-sm">
|
<div id="console-tab" className="break-all px-4 dark:text-white text-stone-900 text-sm py-2">
|
||||||
<pre aria-hidden="true">{`███████╗████████╗██████╗ ██╗ ██╗██████╗ ███████╗██╗
|
<pre aria-hidden="true">{`███████╗████████╗██████╗ ██╗ ██╗██████╗ ███████╗██╗
|
||||||
██╔════╝╚══██╔══╝██╔══██╗██║ ██║██╔══██╗██╔════╝██║
|
██╔════╝╚══██╔══╝██╔══██╗██║ ██║██╔══██╗██╔════╝██║
|
||||||
███████╗ ██║ ██████╔╝██║ ██║██║ ██║█████╗ ██║
|
███████╗ ██║ ██████╔╝██║ ██║██║ ██║█████╗ ██║
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import XMarkIcon from '@heroicons/react/20/solid/XMarkIcon';
|
|
||||||
import { logger } from '@strudel/core';
|
import { logger } from '@strudel/core';
|
||||||
import useEvent from '@src/useEvent.mjs';
|
import useEvent from '@src/useEvent.mjs';
|
||||||
import cx from '@src/cx.mjs';
|
import cx from '@src/cx.mjs';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { useCallback, useLayoutEffect, useEffect, useRef, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { setActiveFooter, useSettings } from '../../../settings.mjs';
|
import { setPanelPinned, setActiveFooter as setTab, useSettings } from '../../../settings.mjs';
|
||||||
import { ConsoleTab } from './ConsoleTab';
|
import { ConsoleTab } from './ConsoleTab';
|
||||||
import { FilesTab } from './FilesTab';
|
import { FilesTab } from './FilesTab';
|
||||||
import { Reference } from './Reference';
|
import { Reference } from './Reference';
|
||||||
@ -12,33 +11,87 @@ import { SettingsTab } from './SettingsTab';
|
|||||||
import { SoundsTab } from './SoundsTab';
|
import { SoundsTab } from './SoundsTab';
|
||||||
import { WelcomeTab } from './WelcomeTab';
|
import { WelcomeTab } from './WelcomeTab';
|
||||||
import { PatternsTab } from './PatternsTab';
|
import { PatternsTab } from './PatternsTab';
|
||||||
import useClient from '@src/useClient.mjs';
|
import { ChevronLeftIcon } from '@heroicons/react/16/solid';
|
||||||
|
|
||||||
// https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85
|
|
||||||
export const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
|
||||||
|
|
||||||
const TAURI = typeof window !== 'undefined' && window.__TAURI__;
|
const TAURI = typeof window !== 'undefined' && window.__TAURI__;
|
||||||
|
|
||||||
export function Panel({ context }) {
|
export function HorizontalPanel({ context }) {
|
||||||
const footerContent = useRef();
|
const settings = useSettings();
|
||||||
|
const { isPanelPinned: pinned, activeFooter: tab } = settings;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelNav
|
||||||
|
className={cx(
|
||||||
|
'hover:max-h-[360px] hover:min-h-[360px] justify-between flex flex-col',
|
||||||
|
pinned ? `min-h-[360px] max-h-[360px]` : 'min-h-10 max-h-10',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex h-full overflow-auto ">
|
||||||
|
<PanelContent context={context} tab={tab} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between min-h-10 max-h-10 pr-2 items-center">
|
||||||
|
<Tabs setTab={setTab} tab={tab} pinned={pinned} />
|
||||||
|
<PinButton pinned={pinned} setPinned={setPanelPinned} />
|
||||||
|
</div>
|
||||||
|
</PanelNav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VerticalPanel({ context }) {
|
||||||
|
const settings = useSettings();
|
||||||
|
const { isPanelPinned: pinned, activeFooter: tab } = settings;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelNav
|
||||||
|
className={cx(
|
||||||
|
'hover:min-w-[min(600px,80vw)] hover:max-w-[min(600px,80vw)]',
|
||||||
|
pinned ? `min-w-[min(600px,80vw)] max-w-[min(600px,80vw)]` : 'min-w-8',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={cx('group-hover:flex flex-col h-full', pinned ? 'flex' : 'hidden')}>
|
||||||
|
<div className="flex justify-between w-full ">
|
||||||
|
<Tabs setTab={setTab} tab={tab} pinned={pinned} />
|
||||||
|
<PinButton pinned={pinned} setPinned={setPanelPinned} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="overflow-auto h-full">
|
||||||
|
<PanelContent context={context} tab={tab} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={cx(pinned ? 'hidden' : 'flex flex-col items-center justify-center h-full group-hover:hidden ')}>
|
||||||
|
<ChevronLeftIcon className="text-foreground opacity-50 w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
</PanelNav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabNames = {
|
||||||
|
welcome: 'intro',
|
||||||
|
patterns: 'patterns',
|
||||||
|
sounds: 'sounds',
|
||||||
|
reference: 'reference',
|
||||||
|
console: 'console',
|
||||||
|
settings: 'settings',
|
||||||
|
};
|
||||||
|
if (TAURI) {
|
||||||
|
tabNames.files = 'files';
|
||||||
|
}
|
||||||
|
|
||||||
|
function PanelNav({ children, className, ...props }) {
|
||||||
|
return (
|
||||||
|
<nav
|
||||||
|
aria-label="Settings Menu"
|
||||||
|
className={cx('bg-lineHighlight group transition-all overflow-x-auto', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PanelContent({ context, tab }) {
|
||||||
const [log, setLog] = useState([]);
|
const [log, setLog] = useState([]);
|
||||||
const { activeFooter, isZen, panelPosition } = useSettings();
|
|
||||||
|
|
||||||
useIsomorphicLayoutEffect(() => {
|
|
||||||
if (footerContent.current && activeFooter === 'console') {
|
|
||||||
// scroll log box to bottom when log changes
|
|
||||||
footerContent.current.scrollTop = footerContent.current?.scrollHeight;
|
|
||||||
}
|
|
||||||
}, [log, activeFooter]);
|
|
||||||
useIsomorphicLayoutEffect(() => {
|
|
||||||
if (!footerContent.current) {
|
|
||||||
} else if (activeFooter === 'console') {
|
|
||||||
footerContent.current.scrollTop = footerContent.current?.scrollHeight;
|
|
||||||
} else {
|
|
||||||
footerContent.current.scrollTop = 0;
|
|
||||||
}
|
|
||||||
}, [activeFooter]);
|
|
||||||
|
|
||||||
useLogger(
|
useLogger(
|
||||||
useCallback((e) => {
|
useCallback((e) => {
|
||||||
const { message, type, data } = e.detail;
|
const { message, type, data } = e.detail;
|
||||||
@ -60,66 +113,72 @@ export function Panel({ context }) {
|
|||||||
}, []),
|
}, []),
|
||||||
);
|
);
|
||||||
|
|
||||||
const PanelTab = ({ children, name, label }) => (
|
switch (tab) {
|
||||||
|
case tabNames.patterns:
|
||||||
|
return <PatternsTab context={context} />;
|
||||||
|
case tabNames.console:
|
||||||
|
return <ConsoleTab log={log} />;
|
||||||
|
case tabNames.sounds:
|
||||||
|
return <SoundsTab />;
|
||||||
|
case tabNames.reference:
|
||||||
|
return <Reference />;
|
||||||
|
case tabNames.settings:
|
||||||
|
return <SettingsTab started={context.started} />;
|
||||||
|
case tabNames.files:
|
||||||
|
return <FilesTab />;
|
||||||
|
default:
|
||||||
|
return <WelcomeTab context={context} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function PanelTab({ label, isSelected, onClick }) {
|
||||||
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
onClick={() => setActiveFooter(name)}
|
onClick={onClick}
|
||||||
className={cx(
|
className={cx(
|
||||||
'h-8 px-2 text-foreground cursor-pointer hover:opacity-50 flex items-center space-x-1 border-b',
|
'h-8 px-2 text-foreground cursor-pointer hover:opacity-50 flex items-center space-x-1 border-b',
|
||||||
activeFooter === name ? 'border-foreground' : 'border-transparent',
|
isSelected ? 'border-foreground' : 'border-transparent',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{label || name}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
{activeFooter === name && <>{children}</>}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
const client = useClient();
|
}
|
||||||
if (isZen) {
|
function Tabs({ setTab, tab }) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isActive = activeFooter !== '';
|
|
||||||
|
|
||||||
let positions = {
|
|
||||||
right: cx('max-w-full flex-grow-0 flex-none overflow-hidden', isActive ? 'w-[600px] h-full' : 'absolute right-0'),
|
|
||||||
bottom: cx('relative', isActive ? 'h-[360px] min-h-[360px]' : ''),
|
|
||||||
};
|
|
||||||
if (!client) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<nav className={cx('bg-lineHighlight z-[10] flex flex-col', positions[panelPosition])}>
|
<div className={cx('flex select-none max-w-full overflow-auto pb-2')}>
|
||||||
<div className="flex justify-between px-2">
|
{Object.keys(tabNames).map((key) => {
|
||||||
<div className={cx('flex select-none max-w-full overflow-auto', activeFooter && 'pb-2')}>
|
const val = tabNames[key];
|
||||||
<PanelTab name="intro" label="welcome" />
|
return <PanelTab key={key} isSelected={tab === val} label={key} onClick={() => setTab(val)} />;
|
||||||
<PanelTab name="patterns" />
|
})}
|
||||||
<PanelTab name="sounds" />
|
</div>
|
||||||
<PanelTab name="console" />
|
);
|
||||||
<PanelTab name="reference" />
|
}
|
||||||
<PanelTab name="settings" />
|
|
||||||
{TAURI && <PanelTab name="files" />}
|
function PinButton({ pinned, setPinned }) {
|
||||||
</div>
|
return (
|
||||||
{activeFooter !== '' && (
|
<button
|
||||||
<button onClick={() => setActiveFooter('')} className="text-foreground px-2" aria-label="Close Panel">
|
onClick={() => setPinned(!pinned)}
|
||||||
<XMarkIcon className="w-5 h-5" />
|
className={cx(
|
||||||
</button>
|
'text-foreground max-h-8 min-h-8 max-w-8 min-w-8 items-center justify-center p-1.5 group-hover:flex',
|
||||||
)}
|
pinned ? 'flex' : 'hidden',
|
||||||
</div>
|
|
||||||
{activeFooter !== '' && (
|
|
||||||
<div className="relative overflow-hidden">
|
|
||||||
<div className="text-white overflow-auto h-full max-w-full" ref={footerContent}>
|
|
||||||
{activeFooter === 'intro' && <WelcomeTab context={context} />}
|
|
||||||
{activeFooter === 'patterns' && <PatternsTab context={context} />}
|
|
||||||
{activeFooter === 'console' && <ConsoleTab log={log} />}
|
|
||||||
{activeFooter === 'sounds' && <SoundsTab />}
|
|
||||||
{activeFooter === 'reference' && <Reference />}
|
|
||||||
{activeFooter === 'settings' && <SettingsTab started={context.started} />}
|
|
||||||
{activeFooter === 'files' && <FilesTab />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</nav>
|
aria-label="Pin Settings Menu"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
stroke="currentColor"
|
||||||
|
fill={'currentColor'}
|
||||||
|
strokeWidth="0"
|
||||||
|
className="w-full h-full"
|
||||||
|
opacity={pinned ? 1 : '.3'}
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M9.828.722a.5.5 0 0 1 .354.146l4.95 4.95a.5.5 0 0 1 0 .707c-.48.48-1.072.588-1.503.588-.177 0-.335-.018-.46-.039l-3.134 3.134a6 6 0 0 1 .16 1.013c.046.702-.032 1.687-.72 2.375a.5.5 0 0 1-.707 0l-2.829-2.828-3.182 3.182c-.195.195-1.219.902-1.414.707s.512-1.22.707-1.414l3.182-3.182-2.828-2.829a.5.5 0 0 1 0-.707c.688-.688 1.673-.767 2.375-.72a6 6 0 0 1 1.013.16l3.134-3.133a3 3 0 0 1-.04-.461c0-.43.108-1.022.589-1.503a.5.5 0 0 1 .353-.146"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -77,7 +77,7 @@ function PatternButtons({ patterns, activePattern, onClick, started }) {
|
|||||||
|
|
||||||
function ActionButton({ children, onClick, label, labelIsHidden }) {
|
function ActionButton({ children, onClick, label, labelIsHidden }) {
|
||||||
return (
|
return (
|
||||||
<button className="hover:opacity-50" onClick={onClick} title={label}>
|
<button className="hover:opacity-50 text-nowrap" onClick={onClick} title={label}>
|
||||||
{labelIsHidden !== true && label}
|
{labelIsHidden !== true && label}
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
@ -102,7 +102,7 @@ export function PatternsTab({ context }) {
|
|||||||
const autoResetPatternOnChange = !isUdels();
|
const autoResetPatternOnChange = !isUdels();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-4 w-full dark:text-white text-stone-900 space-y-2 pb-4 flex flex-col overflow-hidden max-h-full">
|
<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">
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
value={patternFilter}
|
value={patternFilter}
|
||||||
onChange={(value) => settingsMap.setKey('patternFilter', value)}
|
onChange={(value) => settingsMap.setKey('patternFilter', value)}
|
||||||
@ -155,7 +155,7 @@ export function PatternsTab({ context }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<section className="flex overflow-y-scroll max-h-full flex-col">
|
<section className="flex overflow-y-auto max-h-full flex-grow flex-col">
|
||||||
{patternFilter === patternFilterName.user && (
|
{patternFilter === patternFilterName.user && (
|
||||||
<PatternButtons
|
<PatternButtons
|
||||||
onClick={(id) =>
|
onClick={(id) =>
|
||||||
|
|||||||
@ -25,32 +25,37 @@ export function Reference() {
|
|||||||
}, [search]);
|
}, [search]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full pt-2 text-foreground overflow-hidden">
|
<div className="flex h-full w-full p-2 text-foreground overflow-hidden">
|
||||||
<div className="w-42 flex-none h-full overflow-y-auto overflow-x-hidden pr-4">
|
<div className="h-full flex flex-col gap-2 w-1/3 max-w-72 ">
|
||||||
<div class="w-full ml-2 mb-2 top-0 sticky">
|
<div class="w-full flex">
|
||||||
<input
|
<input
|
||||||
className="w-full p-1 bg-background rounded-md"
|
className="w-full p-1 bg-background rounded-md border-none"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
value={search}
|
value={search}
|
||||||
onInput={(event) => setSearch(event.target.value)}
|
onInput={(event) => setSearch(event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{visibleFunctions.map((entry, i) => (
|
<div className="flex flex-col h-full overflow-y-auto gap-1.5 bg-background bg-opacity-50 rounded-md">
|
||||||
<a
|
{visibleFunctions.map((entry, i) => (
|
||||||
key={i}
|
<a
|
||||||
className="cursor-pointer block hover:bg-lineHighlight py-1 px-4"
|
key={i}
|
||||||
onClick={() => {
|
className="cursor-pointer flex-none hover:bg-lineHighlight overflow-x-hidden px-1 text-ellipsis"
|
||||||
const el = document.getElementById(`doc-${i}`);
|
onClick={() => {
|
||||||
const container = document.getElementById('reference-container');
|
const el = document.getElementById(`doc-${i}`);
|
||||||
container.scrollTo(0, el.offsetTop);
|
const container = document.getElementById('reference-container');
|
||||||
}}
|
container.scrollTo(0, el.offsetTop);
|
||||||
>
|
}}
|
||||||
{entry.name} {/* <span className="text-gray-600">{entry.meta.filename}</span> */}
|
>
|
||||||
</a>
|
{entry.name} {/* <span className="text-gray-600">{entry.meta.filename}</span> */}
|
||||||
))}
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="break-normal w-full h-full overflow-auto pl-4 flex relative" id="reference-container">
|
<div
|
||||||
<div className="prose dark:prose-invert max-w-full pr-4">
|
className="break-normal flex-grow flex-col overflow-y-auto overflow-x-hidden px-2 flex relative"
|
||||||
|
id="reference-container"
|
||||||
|
>
|
||||||
|
<div className="prose dark:prose-invert min-w-full px-1 ">
|
||||||
<h2>API Reference</h2>
|
<h2>API Reference</h2>
|
||||||
<p>
|
<p>
|
||||||
This is the long list functions you can use! Remember that you don't need to remember all of those and that
|
This is the long list functions you can use! Remember that you don't need to remember all of those and that
|
||||||
|
|||||||
@ -105,7 +105,7 @@ export function SettingsTab({ started }) {
|
|||||||
const shouldAlwaysSync = isUdels();
|
const shouldAlwaysSync = isUdels();
|
||||||
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
|
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
|
||||||
return (
|
return (
|
||||||
<div className="text-foreground p-4 space-y-4">
|
<div className="text-foreground p-4 space-y-4 w-full">
|
||||||
{canChangeAudioDevice && (
|
{canChangeAudioDevice && (
|
||||||
<FormItem label="Audio Output Device">
|
<FormItem label="Audio Output Device">
|
||||||
<AudioDeviceSelector
|
<AudioDeviceSelector
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import useEvent from '@src/useEvent.mjs';
|
import useEvent from '@src/useEvent.mjs';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { getAudioContext, soundMap, connectToDestination } from '@strudel/webaudio';
|
import { getAudioContext, soundMap, connectToDestination } from '@strudel/webaudio';
|
||||||
import React, { useMemo, useRef, useState } from 'react';
|
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';
|
||||||
@ -53,14 +53,13 @@ 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">
|
||||||
<div className="w-full ml-2 mb-2 top-0 sticky">
|
<input
|
||||||
<input
|
className="w-full p-1 bg-background rounded-md pb-2"
|
||||||
className="w-full p-1 bg-background rounded-md"
|
placeholder="Search"
|
||||||
placeholder="Search"
|
value={search}
|
||||||
value={search}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="pb-2 flex shrink-0 flex-wrap">
|
<div className="pb-2 flex shrink-0 flex-wrap">
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
value={soundsFilter}
|
value={soundsFilter}
|
||||||
@ -74,7 +73,8 @@ export function SoundsTab() {
|
|||||||
></ButtonGroup>
|
></ButtonGroup>
|
||||||
<ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} />
|
<ImportSoundsButton onComplete={() => settingsMap.setKey('soundsFilter', 'user')} />
|
||||||
</div>
|
</div>
|
||||||
<div className="min-h-0 max-h-full grow overflow-auto font-mono text-sm break-normal">
|
|
||||||
|
<div className="min-h-0 max-h-full grow overflow-auto font-mono text-sm break-normal pb-2">
|
||||||
{soundEntries.map(([name, { data, onTrigger }]) => {
|
{soundEntries.map(([name, { data, onTrigger }]) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
|
|||||||
@ -5,7 +5,7 @@ const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL
|
|||||||
|
|
||||||
export function WelcomeTab({ context }) {
|
export function WelcomeTab({ context }) {
|
||||||
return (
|
return (
|
||||||
<div className="prose dark:prose-invert max-w-[600px] pt-2 font-sans pb-8 px-4">
|
<div className="prose dark:prose-invert min-w-full pt-2 font-sans pb-8 px-4 ">
|
||||||
<h3>
|
<h3>
|
||||||
<span className={cx('animate-spin inline-block select-none')}>🌀</span> welcome
|
<span className={cx('animate-spin inline-block select-none')}>🌀</span> welcome
|
||||||
</h3>
|
</h3>
|
||||||
|
|||||||
@ -30,7 +30,8 @@ export const defaultSettings = {
|
|||||||
isZen: false,
|
isZen: false,
|
||||||
soundsFilter: 'all',
|
soundsFilter: 'all',
|
||||||
patternFilter: 'community',
|
patternFilter: 'community',
|
||||||
panelPosition: 'right',
|
panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom',
|
||||||
|
isPanelPinned: true,
|
||||||
userPatterns: '{}',
|
userPatterns: '{}',
|
||||||
audioDeviceName: defaultAudioDeviceName,
|
audioDeviceName: defaultAudioDeviceName,
|
||||||
audioEngineTarget: audioEngineTargets.webaudio,
|
audioEngineTarget: audioEngineTargets.webaudio,
|
||||||
@ -60,6 +61,7 @@ export function useSettings() {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
isZen: parseBoolean(state.isZen),
|
isZen: parseBoolean(state.isZen),
|
||||||
|
isPanelPinned: parseBoolean(state.isPanelPinned),
|
||||||
isBracketMatchingEnabled: parseBoolean(state.isBracketMatchingEnabled),
|
isBracketMatchingEnabled: parseBoolean(state.isBracketMatchingEnabled),
|
||||||
isBracketClosingEnabled: parseBoolean(state.isBracketClosingEnabled),
|
isBracketClosingEnabled: parseBoolean(state.isBracketClosingEnabled),
|
||||||
isLineNumbersDisplayed: parseBoolean(state.isLineNumbersDisplayed),
|
isLineNumbersDisplayed: parseBoolean(state.isLineNumbersDisplayed),
|
||||||
@ -77,6 +79,7 @@ export function useSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
|
export const setActiveFooter = (tab) => settingsMap.setKey('activeFooter', tab);
|
||||||
|
export const setPanelPinned = (isPinned) => settingsMap.setKey('isPanelPinned', isPinned);
|
||||||
|
|
||||||
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
|
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,7 @@
|
|||||||
}
|
}
|
||||||
:root {
|
:root {
|
||||||
--app-height: 100vh;
|
--app-height: 100vh;
|
||||||
|
--app-width: 100vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
#console-tab {
|
#console-tab {
|
||||||
|
|||||||
@ -34,6 +34,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
spacing: {
|
spacing: {
|
||||||
'app-height': 'var(--app-height)',
|
'app-height': 'var(--app-height)',
|
||||||
|
'app-width': 'var(--app-width)',
|
||||||
},
|
},
|
||||||
typography(theme) {
|
typography(theme) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user