tweaks: consistent font sizes and row heights, persist selected reference tag, add wrap prop to ButtonGroup
This commit is contained in:
parent
cb46dd7840
commit
9fc084b291
9 changed files with 68 additions and 55 deletions
|
|
@ -69,7 +69,7 @@ export function Header({ context, isEmbedded = false }) {
|
||||||
<button
|
<button
|
||||||
onClick={handleTogglePlay}
|
onClick={handleTogglePlay}
|
||||||
title={started ? 'stop' : 'play'}
|
title={started ? 'stop' : 'play'}
|
||||||
className={cx('hover:opacity-50', !started && !isCSSAnimationDisabled && 'animate-pulse')}
|
className={cx('px-2 hover:opacity-50', !started && !isCSSAnimationDisabled && 'animate-pulse')}
|
||||||
>
|
>
|
||||||
{!pending ? (
|
{!pending ? (
|
||||||
<span className={cx('flex items-center space-x-2')}>
|
<span className={cx('flex items-center space-x-2')}>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import cx from '@src/cx.mjs';
|
||||||
|
|
||||||
export function ActionButton({ children, label, labelIsHidden, className, ...buttonProps }) {
|
export function ActionButton({ children, label, labelIsHidden, className, ...buttonProps }) {
|
||||||
return (
|
return (
|
||||||
<button className={cx('hover:opacity-50 text-nowrap w-fit', className)} title={label} {...buttonProps}>
|
<button className={cx('hover:opacity-50 text-xs text-nowrap w-fit', className)} title={label} {...buttonProps}>
|
||||||
{labelIsHidden !== true && label}
|
{labelIsHidden !== true && label}
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import cx from '@src/cx.mjs';
|
import cx from '@src/cx.mjs';
|
||||||
|
|
||||||
export function ButtonGroup({ value, onChange, items }) {
|
export function ButtonGroup({ value, onChange, items, wrap = false }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex max-w-lg space-x-0">
|
<div className={cx('flex max-w-lg space-x-0 text-xs', wrap && 'flex-wrap')}>
|
||||||
{Object.entries(items).map(([key, label], i, arr) => (
|
{Object.entries(items).map(([key, label], i, arr) => (
|
||||||
<button
|
<button
|
||||||
key={key}
|
key={key}
|
||||||
id={key}
|
id={key}
|
||||||
onClick={() => onChange(key)}
|
onClick={() => onChange(key)}
|
||||||
className={cx(
|
className={cx(
|
||||||
'px-2 border-b-2 h-8 whitespace-nowrap',
|
'px-2 border-b-2 h-8 whitespace-nowrap border-box max-h-8 hover:opacity-50',
|
||||||
// i === 0 && 'rounded-l-md',
|
// i === 0 && 'rounded-l-md',
|
||||||
// i === arr.length - 1 && 'rounded-r-md',
|
// i === arr.length - 1 && 'rounded-r-md',
|
||||||
// value === key ? 'bg-background' : 'bg-lineHighlight',
|
// value === key ? 'bg-background' : 'bg-lineHighlight',
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ function PanelTab({ label, isSelected, onClick }) {
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={cx(
|
className={cx(
|
||||||
'h-8 px-2 text-sm text-foreground cursor-pointer hover:opacity-50 flex items-center space-x-1 border-b-2',
|
'h-8 px-2 text-xs text-foreground cursor-pointer hover:opacity-50 flex items-center space-x-1 border-b-2',
|
||||||
isSelected ? 'border-foreground' : 'border-transparent',
|
isSelected ? 'border-foreground' : 'border-transparent',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
useViewingPatternData,
|
useViewingPatternData,
|
||||||
userPattern,
|
userPattern,
|
||||||
} from '../../../user_pattern_utils.mjs';
|
} from '../../../user_pattern_utils.mjs';
|
||||||
import { useMemo } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { getMetadata } from '../../../metadata_parser.js';
|
import { getMetadata } from '../../../metadata_parser.js';
|
||||||
import { useExamplePatterns } from '../../useExamplePatterns.jsx';
|
import { useExamplePatterns } from '../../useExamplePatterns.jsx';
|
||||||
import { parseJSON, isUdels } from '../../util.mjs';
|
import { parseJSON, isUdels } from '../../util.mjs';
|
||||||
|
|
@ -123,10 +123,11 @@ export function PatternsTab({ context }) {
|
||||||
);
|
);
|
||||||
}, [search, viewingPatternStore]);
|
}, [search, viewingPatternStore]);
|
||||||
|
|
||||||
|
const importRef = useRef();
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full text-foreground flex flex-col overflow-hidden">
|
<div className="w-full h-full text-foreground flex flex-col overflow-hidden">
|
||||||
<Textbox className="w-full border-0" placeholder="Search" value={search} onChange={setSearch} />
|
<Textbox className="w-full border-0" placeholder="Search..." value={search} onChange={setSearch} />
|
||||||
<div className="px-2 shrink-0 py-2 space-x-4 flex max-w-full overflow-x-auto border-y border-muted">
|
<div className="px-2 shrink-0 h-8 space-x-4 flex max-w-full overflow-x-auto border-y border-muted">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
label="new"
|
label="new"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -148,16 +149,15 @@ export function PatternsTab({ context }) {
|
||||||
updateCodeWindow(context, { ...data, collection: userPattern.collection });
|
updateCodeWindow(context, { ...data, collection: userPattern.collection });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<label className="hover:opacity-50 cursor-pointer">
|
<input
|
||||||
<input
|
ref={importRef}
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
type="file"
|
type="file"
|
||||||
multiple
|
multiple
|
||||||
accept="text/plain,text/x-markdown,application/json"
|
accept="text/plain,text/x-markdown,application/json"
|
||||||
onChange={(e) => importPatterns(e.target.files)}
|
onChange={(e) => importPatterns(e.target.files)}
|
||||||
/>
|
/>
|
||||||
import
|
<ActionButton label="import" onClick={() => importRef.current.click()} />
|
||||||
</label>
|
|
||||||
<ActionButton label="export" onClick={exportPatterns} />
|
<ActionButton label="export" onClick={exportPatterns} />
|
||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { memo, useEffect, useMemo, useState, Fragment } from 'react';
|
||||||
|
|
||||||
import jsdocJson from '../../../../../doc.json';
|
import jsdocJson from '../../../../../doc.json';
|
||||||
import { Textbox } from '@src/repl/components/panel/SettingsTab';
|
import { Textbox } from '@src/repl/components/panel/SettingsTab';
|
||||||
|
import { settingsMap, useSettings } from '@src/settings.mjs';
|
||||||
|
|
||||||
const isValid = ({ name, description }) => name && !name.startsWith('_') && !!description;
|
const isValid = ({ name, description }) => name && !name.startsWith('_') && !!description;
|
||||||
|
|
||||||
|
|
@ -37,6 +38,19 @@ const availableFunctions = (() => {
|
||||||
return functions.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name));
|
return functions.sort((a, b) => /* a.meta.filename.localeCompare(b.meta.filename) + */ a.name.localeCompare(b.name));
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const tagCounts = {};
|
||||||
|
// const tagOptions = { all: `all (${availableFunctions.length})` };
|
||||||
|
const tagOptions = { all: `all` };
|
||||||
|
for (const doc of availableFunctions) {
|
||||||
|
(doc.tags || ['untagged']).forEach((t) => {
|
||||||
|
if (typeof t === 'string' && t) {
|
||||||
|
tagCounts[t] = (tagCounts[t] || 0) + 1;
|
||||||
|
//tagOptions[t] = `${t} (${tagCounts[t]})`;
|
||||||
|
tagOptions[t] = t;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const getInnerText = (html) => {
|
const getInnerText = (html) => {
|
||||||
var div = document.createElement('div');
|
var div = document.createElement('div');
|
||||||
div.innerHTML = html;
|
div.innerHTML = html;
|
||||||
|
|
@ -45,40 +59,38 @@ const getInnerText = (html) => {
|
||||||
|
|
||||||
export const Reference = memo(function Reference() {
|
export const Reference = memo(function Reference() {
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [selectedTag, setSelectedTag] = useState(null);
|
|
||||||
const [selectedFunction, setSelectedFunction] = useState(null);
|
const [selectedFunction, setSelectedFunction] = useState(null);
|
||||||
|
const { referenceTag } = useSettings();
|
||||||
|
|
||||||
const toggleTag = (tag) => {
|
const toggleTag = (tag) => {
|
||||||
if (selectedTag === tag) {
|
if (referenceTag === tag) {
|
||||||
setSelectedTag(null);
|
setReferenceTag('all');
|
||||||
} else {
|
} else {
|
||||||
setSelectedTag(tag);
|
setReferenceTag(tag);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchVisibleFunctions = useMemo(() => {
|
const searchVisibleFunctions = useMemo(() => {
|
||||||
return availableFunctions.filter((entry) => {
|
return availableFunctions.filter((entry) => {
|
||||||
if (selectedTag) {
|
if (referenceTag && referenceTag !== 'all') {
|
||||||
if (!(entry.tags || ['untagged']).includes(selectedTag)) {
|
if (!(entry.tags || ['untagged']).includes(referenceTag)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!search) {
|
if (!search) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lowerCaseSearch = search.toLowerCase();
|
const lowerCaseSearch = search.toLowerCase();
|
||||||
return (
|
return (
|
||||||
entry.name.toLowerCase().includes(lowerCaseSearch) ||
|
entry.name.toLowerCase().includes(lowerCaseSearch) ||
|
||||||
(entry.synonyms?.some((s) => s.toLowerCase().includes(lowerCaseSearch)) ?? false)
|
(entry.synonyms?.some((s) => s.toLowerCase().includes(lowerCaseSearch)) ?? false)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, [search, selectedTag]);
|
}, [search, referenceTag]);
|
||||||
|
|
||||||
const detailVisibleFunctions = useMemo(() => {
|
const detailVisibleFunctions = useMemo(() => {
|
||||||
return searchVisibleFunctions.filter((x) => {
|
return searchVisibleFunctions.filter((x) => {
|
||||||
if (selectedTag === null) {
|
if (referenceTag === null || referenceTag === 'all') {
|
||||||
if (search) {
|
if (search) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -87,19 +99,10 @@ export const Reference = memo(function Reference() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [searchVisibleFunctions, selectedFunction, selectedTag]);
|
}, [searchVisibleFunctions, selectedFunction, referenceTag]);
|
||||||
|
|
||||||
const tagCounts = {};
|
|
||||||
for (const doc of availableFunctions) {
|
|
||||||
(doc.tags || ['untagged']).forEach((t) => {
|
|
||||||
if (typeof t === 'string' && t) {
|
|
||||||
tagCounts[t] = (tagCounts[t] || 0) + 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSearchTagFilterClick = () => {
|
const onSearchTagFilterClick = () => {
|
||||||
setSelectedTag(null);
|
setReferenceTag('all');
|
||||||
setSelectedFunction(null);
|
setSelectedFunction(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -111,6 +114,8 @@ export const Reference = memo(function Reference() {
|
||||||
}
|
}
|
||||||
}, [selectedFunction]);
|
}, [selectedFunction]);
|
||||||
|
|
||||||
|
let setReferenceTag = (value) => settingsMap.setKey('referenceTag', value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full overflow-hidden">
|
<div className="flex h-full w-full overflow-hidden">
|
||||||
<div className="h-full text-foreground flex flex-col w-1/3 border-r border-muted">
|
<div className="h-full text-foreground flex flex-col w-1/3 border-r border-muted">
|
||||||
|
|
@ -118,30 +123,35 @@ export const Reference = memo(function Reference() {
|
||||||
<div className="w-full flex">
|
<div className="w-full flex">
|
||||||
<Textbox
|
<Textbox
|
||||||
className="w-full border-0 border-b border-muted"
|
className="w-full border-0 border-b border-muted"
|
||||||
placeholder="Search"
|
placeholder="Search..."
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setSelectedFunction(null);
|
setSelectedFunction(null);
|
||||||
|
setReferenceTag('all');
|
||||||
setSearch(e);
|
setSearch(e);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{selectedTag && (
|
|
||||||
|
{/* <div className="flex shrink-0 flex-wrap w-full overflow-auto border-y border-muted">
|
||||||
|
<ButtonGroup wrap value={referenceTag} onChange={setReferenceTag} items={tagOptions}></ButtonGroup>
|
||||||
|
</div> */}
|
||||||
|
{referenceTag && referenceTag !== 'all' && (
|
||||||
<div className="w-72">
|
<div className="w-72">
|
||||||
<span
|
<span
|
||||||
className="text-foreground border border-muted border-t-0 border-l-0 px-1 py-0.5 my-2 cursor-pointer font-sans"
|
className="text-foreground border border-muted border-t-0 border-l-0 px-1 py-0.5 my-2 cursor-pointer font-sans"
|
||||||
onClick={onSearchTagFilterClick}
|
onClick={onSearchTagFilterClick}
|
||||||
>
|
>
|
||||||
{selectedTag}
|
{referenceTag}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col h-full py-2 overflow-y-auto gap-1.5 bg-opacity-50">
|
<div className="h-full p-2 overflow-y-auto bg-opacity-50">
|
||||||
{searchVisibleFunctions.map((entry, i) => (
|
{searchVisibleFunctions.map((entry, i) => (
|
||||||
<Fragment key={`entry-${entry.name}`}>
|
<Fragment key={`entry-${entry.name}`}>
|
||||||
<a
|
<a
|
||||||
className={
|
className={
|
||||||
'cursor-pointer flex-none hover:bg-lineHighlight overflow-x-hidden px-1 text-ellipsis ' +
|
'cursor-pointer hover:opacity-50 text-ellipsis block' +
|
||||||
(entry.name === selectedFunction ? 'bg-lineHighlight font-bold' : '')
|
(entry.name === selectedFunction ? 'bg-lineHighlight font-bold' : '')
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -159,7 +169,7 @@ export const Reference = memo(function Reference() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="break-normal flex-col overflow-y-auto overflow-x-hidden p-2 flex relative"
|
className="w-2/3 break-normal flex-col overflow-y-auto overflow-x-hidden p-2 flex relative"
|
||||||
id="reference-container"
|
id="reference-container"
|
||||||
>
|
>
|
||||||
<div className="prose dark:prose-invert min-w-full px-1 text-sm">
|
<div className="prose dark:prose-invert min-w-full px-1 text-sm">
|
||||||
|
|
@ -169,6 +179,7 @@ export const Reference = memo(function Reference() {
|
||||||
that you can already make music with a small set of functions!
|
that you can already make music with a small set of functions!
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
|
{/* <ButtonGroup wrap value={referenceTag} onChange={setReferenceTag} items={tagOptions}/>*/}
|
||||||
{Object.entries(tagCounts)
|
{Object.entries(tagCounts)
|
||||||
.sort(([a], [b]) => a.localeCompare(b))
|
.sort(([a], [b]) => a.localeCompare(b))
|
||||||
.map(([t, count]) => (
|
.map(([t, count]) => (
|
||||||
|
|
@ -176,7 +187,7 @@ export const Reference = memo(function Reference() {
|
||||||
<a
|
<a
|
||||||
className={[
|
className={[
|
||||||
'select-none text-white border border-muted px-1 py-0.5 my-2 cursor-pointer text-sm/8 no-underline font-sans',
|
'select-none text-white border border-muted px-1 py-0.5 my-2 cursor-pointer text-sm/8 no-underline font-sans',
|
||||||
`${selectedTag === t ? 'bg-muted text-foreground' : ''}`,
|
`${referenceTag === t ? 'bg-muted text-foreground' : ''}`,
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
onClick={() => toggleTag(t)}
|
onClick={() => toggleTag(t)}
|
||||||
>
|
>
|
||||||
|
|
@ -188,7 +199,7 @@ export const Reference = memo(function Reference() {
|
||||||
{detailVisibleFunctions.map((entry, i) => (
|
{detailVisibleFunctions.map((entry, i) => (
|
||||||
<section key={i} className="font-sans">
|
<section key={i} className="font-sans">
|
||||||
<div className="flex flex-row items-center mt-8 justify-between">
|
<div className="flex flex-row items-center mt-8 justify-between">
|
||||||
<h3 className="font-mono my-0" id={`doc-${entry.name}`}>
|
<h3 className="font-mono my-0 pt-4" id={`doc-${entry.name}`}>
|
||||||
{entry.name}
|
{entry.name}
|
||||||
</h3>
|
</h3>
|
||||||
{entry.tags && (
|
{entry.tags && (
|
||||||
|
|
@ -202,7 +213,7 @@ export const Reference = memo(function Reference() {
|
||||||
Synonyms: <code>{entry.synonyms_text}</code>
|
Synonyms: <code>{entry.synonyms_text}</code>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{/* <small>{entry.meta.filename}</small> */}
|
|
||||||
<p dangerouslySetInnerHTML={{ __html: entry.description }}></p>
|
<p dangerouslySetInnerHTML={{ __html: entry.description }}></p>
|
||||||
<ul>
|
<ul>
|
||||||
{entry.params?.map(({ name, type, description }, i) => (
|
{entry.params?.map(({ name, type, description }, i) => (
|
||||||
|
|
@ -212,7 +223,7 @@ export const Reference = memo(function Reference() {
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
{entry.examples?.map((example, j) => (
|
{entry.examples?.map((example, j) => (
|
||||||
<pre className="bg-background" key={j}>
|
<pre className="bg-background border border-muted" key={j}>
|
||||||
{example}
|
{example}
|
||||||
</pre>
|
</pre>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,17 @@ function cx(...classes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputClass =
|
const inputClass =
|
||||||
'bg-background text-sm border rounded-0 text-foreground border-muted placeholder-foreground focus:outline-none focus:ring-0 focus:border-foreground';
|
'bg-background text-xs h-8 max-h-8 border border-box rounded-0 text-foreground border-muted placeholder-muted focus:outline-none focus:ring-0 focus:border-foreground';
|
||||||
|
|
||||||
export function Textbox({ onChange, className, ...inputProps }) {
|
export function Textbox({ onChange, className, ...inputProps }) {
|
||||||
return (
|
return (
|
||||||
<input className={cx('p-2', inputClass, className)} onChange={(e) => onChange(e.target.value)} {...inputProps} />
|
<input className={cx('px-2', inputClass, className)} onChange={(e) => onChange(e.target.value)} {...inputProps} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Checkbox({ label, value, onChange, disabled = false }) {
|
function Checkbox({ label, value, onChange, disabled = false }) {
|
||||||
return (
|
return (
|
||||||
<label className="text-sm">
|
<label className="text-xs">
|
||||||
<input
|
<input
|
||||||
className={cx(
|
className={cx(
|
||||||
'bg-background text-sm border border-muted focus:outline-none focus:ring-0 focus:border-foreground',
|
'bg-background text-sm border border-muted focus:outline-none focus:ring-0 focus:border-foreground',
|
||||||
|
|
@ -96,7 +96,7 @@ function NumberSlider({ value, onChange, step = 1, ...rest }) {
|
||||||
|
|
||||||
function FormItem({ label, children, sublabel }) {
|
function FormItem({ label, children, sublabel }) {
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-2 text-sm">
|
<div className="grid gap-2 text-xs">
|
||||||
<label className="text-sm">{label}</label>
|
<label className="text-sm">{label}</label>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -78,10 +78,11 @@ export function SoundsTab() {
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div id="sounds-tab" className="flex flex-col w-full h-full text-foreground">
|
<div id="sounds-tab" className="flex flex-col w-full h-full text-foreground">
|
||||||
<Textbox placeholder="Search" className="border-0" value={search} onChange={(v) => setSearch(v)} />
|
<Textbox placeholder="Search..." className="border-0" value={search} onChange={(v) => setSearch(v)} />
|
||||||
|
|
||||||
<div className="flex shrink-0 flex-wrap border-y border-muted">
|
<div className="flex shrink-0 flex-wrap border-y border-muted">
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
|
wrap
|
||||||
value={soundsFilter}
|
value={soundsFilter}
|
||||||
onChange={(value) => settingsMap.setKey('soundsFilter', value)}
|
onChange={(value) => settingsMap.setKey('soundsFilter', value)}
|
||||||
items={{
|
items={{
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ export const defaultSettings = {
|
||||||
latestCode: '',
|
latestCode: '',
|
||||||
isZen: false,
|
isZen: false,
|
||||||
soundsFilter: soundFilterType.ALL,
|
soundsFilter: soundFilterType.ALL,
|
||||||
|
referenceTag: 'all',
|
||||||
patternFilter: 'community',
|
patternFilter: 'community',
|
||||||
// panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom', //FIX: does not work on astro
|
// panelPosition: window.innerWidth > 1000 ? 'right' : 'bottom', //FIX: does not work on astro
|
||||||
panelPosition: 'right',
|
panelPosition: 'right',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue