Merge pull request #1199 from tidalcycles/origin/daslyfe/panel_fixes
Make panel hover behavior optional
This commit is contained in:
commit
1d05051fd6
6 changed files with 134 additions and 47 deletions
|
|
@ -13,7 +13,7 @@ export default function ReplEditor(Props) {
|
||||||
const { context } = Props;
|
const { context } = Props;
|
||||||
const { containerRef, editorRef, error, init, pending } = context;
|
const { containerRef, editorRef, error, init, pending } = context;
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const { panelPosition } = settings;
|
const { panelPosition, isZen } = settings;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col relative">
|
<div className="h-full flex flex-col relative">
|
||||||
|
|
@ -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' && <VerticalPanel context={context} />}
|
{!isZen && panelPosition === 'right' && <VerticalPanel context={context} />}
|
||||||
</div>
|
</div>
|
||||||
<UserFacingErrorMessage error={error} />
|
<UserFacingErrorMessage error={error} />
|
||||||
{panelPosition === 'bottom' && <HorizontalPanel context={context} />}
|
{!isZen && panelPosition === 'bottom' && <HorizontalPanel context={context} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export function ButtonGroup({ value, onChange, items }) {
|
||||||
{Object.entries(items).map(([key, label], i, arr) => (
|
{Object.entries(items).map(([key, label], i, arr) => (
|
||||||
<button
|
<button
|
||||||
key={key}
|
key={key}
|
||||||
|
id={key}
|
||||||
onClick={() => onChange(key)}
|
onClick={() => onChange(key)}
|
||||||
className={cx(
|
className={cx(
|
||||||
'px-2 border-b h-8 whitespace-nowrap',
|
'px-2 border-b h-8 whitespace-nowrap',
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ 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, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { setPanelPinned, setActiveFooter as setTab, useSettings } from '../../../settings.mjs';
|
import { setPanelPinned, setActiveFooter as setTab, setIsPanelOpened, 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';
|
||||||
|
|
@ -11,28 +11,31 @@ 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 { ChevronLeftIcon } from '@heroicons/react/16/solid';
|
import { ChevronLeftIcon, XMarkIcon } from '@heroicons/react/16/solid';
|
||||||
|
|
||||||
const TAURI = typeof window !== 'undefined' && window.__TAURI__;
|
const TAURI = typeof window !== 'undefined' && window.__TAURI__;
|
||||||
|
|
||||||
export function HorizontalPanel({ context }) {
|
export function HorizontalPanel({ context }) {
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const { isPanelPinned: pinned, activeFooter: tab } = settings;
|
const { isPanelOpen, activeFooter: tab } = settings;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelNav
|
<PanelNav
|
||||||
className={cx(
|
settings={settings}
|
||||||
'hover:max-h-[360px] hover:min-h-[360px] justify-between flex flex-col',
|
className={cx(isPanelOpen ? `min-h-[360px] max-h-[360px]` : 'min-h-12 max-h-12', 'overflow-hidden flex flex-col')}
|
||||||
pinned ? `min-h-[360px] max-h-[360px]` : 'min-h-10 max-h-10',
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<div className="flex h-full overflow-auto ">
|
{isPanelOpen && (
|
||||||
<PanelContent context={context} tab={tab} />
|
<div className="flex h-full overflow-auto pr-10 ">
|
||||||
|
<PanelContent context={context} tab={tab} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="absolute right-4 pt-4">
|
||||||
|
<PanelActionButton settings={settings} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-between min-h-10 max-h-10 pr-2 items-center">
|
<div className="flex justify-between min-h-12 max-h-12 grid-cols-2 items-center">
|
||||||
<Tabs setTab={setTab} tab={tab} pinned={pinned} />
|
<Tabs setTab={setTab} tab={tab} />
|
||||||
<PinButton pinned={pinned} setPinned={setPanelPinned} />
|
|
||||||
</div>
|
</div>
|
||||||
</PanelNav>
|
</PanelNav>
|
||||||
);
|
);
|
||||||
|
|
@ -40,28 +43,37 @@ export function HorizontalPanel({ context }) {
|
||||||
|
|
||||||
export function VerticalPanel({ context }) {
|
export function VerticalPanel({ context }) {
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const { isPanelPinned: pinned, activeFooter: tab } = settings;
|
const { activeFooter: tab, isPanelOpen } = settings;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelNav
|
<PanelNav
|
||||||
className={cx(
|
settings={settings}
|
||||||
'hover:min-w-[min(600px,80vw)] hover:max-w-[min(600px,80vw)]',
|
className={cx(isPanelOpen ? `min-w-[min(600px,80vw)] max-w-[min(600px,80vw)]` : 'min-w-12 max-w-12')}
|
||||||
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')}>
|
{isPanelOpen ? (
|
||||||
<div className="flex justify-between w-full ">
|
<div className={cx('flex flex-col h-full')}>
|
||||||
<Tabs setTab={setTab} tab={tab} pinned={pinned} />
|
<div className="flex justify-between w-full ">
|
||||||
<PinButton pinned={pinned} setPinned={setPanelPinned} />
|
<Tabs setTab={setTab} tab={tab} />
|
||||||
</div>
|
<PanelActionButton settings={settings} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="overflow-auto h-full">
|
<div className="overflow-auto h-full">
|
||||||
<PanelContent context={context} tab={tab} />
|
<PanelContent context={context} tab={tab} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
<div className={cx(pinned ? 'hidden' : 'flex flex-col items-center justify-center h-full group-hover:hidden ')}>
|
<button
|
||||||
<ChevronLeftIcon className="text-foreground opacity-50 w-6 h-6" />
|
onClick={(e) => {
|
||||||
</div>
|
setIsPanelOpened(true);
|
||||||
|
}}
|
||||||
|
aria-label="open menu panel"
|
||||||
|
className={cx(
|
||||||
|
'flex flex-col hover:bg-lineBackground items-center cursor-pointer justify-center w-full h-full',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon className="text-foreground opacity-50 w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</PanelNav>
|
</PanelNav>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -78,11 +90,27 @@ if (TAURI) {
|
||||||
tabNames.files = 'files';
|
tabNames.files = 'files';
|
||||||
}
|
}
|
||||||
|
|
||||||
function PanelNav({ children, className, ...props }) {
|
function PanelNav({ children, className, settings, ...props }) {
|
||||||
|
const isHoverBehavior = settings.togglePanelTrigger === 'hover';
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
aria-label="Settings Menu"
|
onClick={() => {
|
||||||
className={cx('bg-lineHighlight group transition-all overflow-x-auto', className)}
|
if (!settings.isPanelOpen) {
|
||||||
|
setIsPanelOpened(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
if (isHoverBehavior && !settings.isPanelOpen) {
|
||||||
|
setIsPanelOpened(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => {
|
||||||
|
if (isHoverBehavior && !settings.isPanelPinned) {
|
||||||
|
setIsPanelOpened(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
aria-label="Menu Panel"
|
||||||
|
className={cx('bg-lineHighlight group overflow-x-auto', className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
@ -134,7 +162,7 @@ function PanelContent({ context, tab }) {
|
||||||
function PanelTab({ label, isSelected, onClick }) {
|
function PanelTab({ label, isSelected, onClick }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<button
|
||||||
onClick={onClick}
|
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',
|
||||||
|
|
@ -142,13 +170,13 @@ function PanelTab({ label, isSelected, onClick }) {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function Tabs({ setTab, tab }) {
|
function Tabs({ setTab, tab, className }) {
|
||||||
return (
|
return (
|
||||||
<div className={cx('flex select-none max-w-full overflow-auto pb-2')}>
|
<div className={cx('flex select-none max-w-full overflow-auto pb-2', className)}>
|
||||||
{Object.keys(tabNames).map((key) => {
|
{Object.keys(tabNames).map((key) => {
|
||||||
const val = tabNames[key];
|
const val = tabNames[key];
|
||||||
return <PanelTab key={key} isSelected={tab === val} label={key} onClick={() => setTab(val)} />;
|
return <PanelTab key={key} isSelected={tab === val} label={key} onClick={() => setTab(val)} />;
|
||||||
|
|
@ -157,15 +185,28 @@ function Tabs({ setTab, tab }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PinButton({ pinned, setPinned }) {
|
function PanelActionButton({ settings }) {
|
||||||
|
const { togglePanelTrigger, isPanelPinned, isPanelOpen } = settings;
|
||||||
|
const isHoverBehavior = togglePanelTrigger === 'hover';
|
||||||
|
if (!isPanelOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHoverBehavior) {
|
||||||
|
return <PinButton pinned={isPanelPinned} />;
|
||||||
|
}
|
||||||
|
return <CloseButton onClick={() => setIsPanelOpened(false)} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PinButton({ pinned }) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={() => setPinned(!pinned)}
|
onClick={() => setPanelPinned(!pinned)}
|
||||||
className={cx(
|
className={cx(
|
||||||
'text-foreground max-h-8 min-h-8 max-w-8 min-w-8 items-center justify-center p-1.5 group-hover:flex',
|
'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',
|
pinned ? 'flex' : 'hidden',
|
||||||
)}
|
)}
|
||||||
aria-label="Pin Settings Menu"
|
aria-label="Pin Menu Panel"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
|
|
@ -182,6 +223,20 @@ function PinButton({ pinned, setPinned }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function CloseButton({ onClick }) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
className={cx(
|
||||||
|
'text-foreground max-h-8 min-h-8 max-w-8 min-w-8 items-center justify-center p-1.5 group-hover:flex',
|
||||||
|
)}
|
||||||
|
aria-label="Close Menu"
|
||||||
|
>
|
||||||
|
<XMarkIcon />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function useLogger(onTrigger) {
|
function useLogger(onTrigger) {
|
||||||
useEvent(logger.key, onTrigger);
|
useEvent(logger.key, onTrigger);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@ export function SettingsTab({ started }) {
|
||||||
panelPosition,
|
panelPosition,
|
||||||
audioDeviceName,
|
audioDeviceName,
|
||||||
audioEngineTarget,
|
audioEngineTarget,
|
||||||
|
togglePanelTrigger,
|
||||||
} = useSettings();
|
} = useSettings();
|
||||||
const shouldAlwaysSync = isUdels();
|
const shouldAlwaysSync = isUdels();
|
||||||
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
|
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
|
||||||
|
|
@ -170,6 +171,31 @@ export function SettingsTab({ started }) {
|
||||||
items={{ bottom: 'Bottom', right: 'Right' }}
|
items={{ bottom: 'Bottom', right: 'Right' }}
|
||||||
></ButtonGroup>
|
></ButtonGroup>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem label="Open Panel on: ">
|
||||||
|
<ButtonGroup
|
||||||
|
value={togglePanelTrigger}
|
||||||
|
onChange={(value) => settingsMap.setKey('togglePanelTrigger', value)}
|
||||||
|
items={{ click: 'Click', hover: 'Hover' }}
|
||||||
|
></ButtonGroup>
|
||||||
|
{/* <Checkbox
|
||||||
|
label="Click"
|
||||||
|
onChange={(cbEvent) => {
|
||||||
|
if (cbEvent.target.checked) {
|
||||||
|
settingsMap.setKey('togglePanelTrigger', 'click');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={togglePanelTrigger != 'hover'}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label="Hover"
|
||||||
|
onChange={(cbEvent) => {
|
||||||
|
if (cbEvent.target.checked) {
|
||||||
|
settingsMap.setKey('togglePanelTrigger', 'hover');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={togglePanelTrigger == 'hover'}
|
||||||
|
/> */}
|
||||||
|
</FormItem>
|
||||||
<FormItem label="Code Settings">
|
<FormItem label="Code Settings">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label="Enable bracket matching"
|
label="Enable bracket matching"
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,9 @@ 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
|
<input
|
||||||
className="w-full p-1 bg-background rounded-md pb-2"
|
className="w-full p-1 bg-background rounded-md my-2"
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,11 @@ export const defaultSettings = {
|
||||||
isZen: false,
|
isZen: false,
|
||||||
soundsFilter: 'all',
|
soundsFilter: 'all',
|
||||||
patternFilter: 'community',
|
patternFilter: 'community',
|
||||||
panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom',
|
// panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom', //FIX: does not work on astro
|
||||||
isPanelPinned: true,
|
panelPosition: 'right',
|
||||||
|
isPanelPinned: false,
|
||||||
|
isPanelOpen: true,
|
||||||
|
togglePanelTrigger: 'click', //click | hover
|
||||||
userPatterns: '{}',
|
userPatterns: '{}',
|
||||||
audioDeviceName: defaultAudioDeviceName,
|
audioDeviceName: defaultAudioDeviceName,
|
||||||
audioEngineTarget: audioEngineTargets.webaudio,
|
audioEngineTarget: audioEngineTargets.webaudio,
|
||||||
|
|
@ -61,7 +64,6 @@ 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),
|
||||||
|
|
@ -74,12 +76,15 @@ export function useSettings() {
|
||||||
isSyncEnabled: isUdels() ? true : parseBoolean(state.isSyncEnabled),
|
isSyncEnabled: isUdels() ? true : parseBoolean(state.isSyncEnabled),
|
||||||
fontSize: Number(state.fontSize),
|
fontSize: Number(state.fontSize),
|
||||||
panelPosition: state.activeFooter !== '' && !isUdels() ? state.panelPosition : 'bottom', // <-- keep this 'bottom' where it is!
|
panelPosition: state.activeFooter !== '' && !isUdels() ? state.panelPosition : 'bottom', // <-- keep this 'bottom' where it is!
|
||||||
|
isPanelPinned: parseBoolean(state.isPanelPinned),
|
||||||
|
isPanelOpen: parseBoolean(state.isPanelOpen),
|
||||||
userPatterns: userPatterns,
|
userPatterns: userPatterns,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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 setPanelPinned = (bool) => settingsMap.setKey('isPanelPinned', bool);
|
||||||
|
export const setIsPanelOpened = (bool) => settingsMap.setKey('isPanelOpen', bool);
|
||||||
|
|
||||||
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
|
export const setIsZen = (active) => settingsMap.setKey('isZen', !!active);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue