Desktop: More tools gets a command search across groups and a compact list layout
This commit is contained in:
parent
e32d0772e9
commit
78b1b599a5
4 changed files with 41 additions and 5 deletions
|
|
@ -72,6 +72,13 @@ export const en = {
|
|||
'tabs.more': "{count} more tabs",
|
||||
'tabs.moreShort': "More",
|
||||
'reviews.keysHint': "Keyboard: J / K next and previous, R reply, C copy, O open in the console",
|
||||
'tools.filter': "Find a command",
|
||||
'tools.search': "Search {count} commands (name, method, what it does)",
|
||||
'tools.searchAria': "Search commands",
|
||||
'tools.density': "Layout",
|
||||
'tools.list': "List",
|
||||
'tools.results': "{count} matching commands",
|
||||
'tools.noHits': "No command matches. Try a store word (listing, prices, testflight...).",
|
||||
'palette.gActions': 'Actions',
|
||||
'palette.openApp': 'Open',
|
||||
|
||||
|
|
|
|||
|
|
@ -74,6 +74,13 @@ export const tr: Record<keyof typeof en, string> = {
|
|||
'tabs.more': "{count} sekme daha",
|
||||
'tabs.moreShort': "Daha fazla",
|
||||
'reviews.keysHint': "Klavye: J / K sonraki ve önceki, R yanıtla, C kopyala, O konsolda aç",
|
||||
'tools.filter': "Komut bul",
|
||||
'tools.search': "{count} komutta ara (ad, yöntem, ne yaptığı)",
|
||||
'tools.searchAria': "Komut ara",
|
||||
'tools.density': "Görünüm",
|
||||
'tools.list': "Liste",
|
||||
'tools.results': "{count} eşleşen komut",
|
||||
'tools.noHits': "Eşleşen komut yok. Bir mağaza kelimesi deneyin (listing, prices, testflight...).",
|
||||
'palette.gActions': 'İşlemler',
|
||||
'palette.openApp': 'Aç',
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import { api, errorText, type MethodDescriptor, newJob, type Project, type ToolR
|
|||
import { type Key, t } from '../../i18n';
|
||||
import { useData, useWrite } from '../../store';
|
||||
import { Icon } from '../../ui/icons';
|
||||
import { ErrorNotice, Skeleton } from '../../ui/kit';
|
||||
import { Section, StatusPill } from '../../ui/ds';
|
||||
import { ErrorNotice, Seg, Skeleton } from '../../ui/kit';
|
||||
import { Section, StatusPill, Toolbar } from '../../ui/ds';
|
||||
import '../../styles/tools.css';
|
||||
import { WriteButton } from '../../ui/WriteButton';
|
||||
|
||||
|
|
@ -111,6 +111,9 @@ export function ToolsTab({ project }: { project: Project }) {
|
|||
const [methods, setMethods] = useState<MethodDescriptor[] | null>(null);
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
const [open, setOpen] = useState<string>(TOOLBOX[0]!.id);
|
||||
const [q, setQ] = useState('');
|
||||
const [dense, setDenseState] = useState<boolean>(() => { try { return localStorage.getItem('sb.tools.dense') === '1'; } catch { return false; } });
|
||||
const setDense = (v: boolean) => { setDenseState(v); try { localStorage.setItem('sb.tools.dense', v ? '1' : '0'); } catch { /* private mode */ } };
|
||||
useEffect(() => {
|
||||
api.call<{ methods: MethodDescriptor[] }>('system.methods').then(r => setMethods(r.methods), e => setErr(errorText(e)));
|
||||
}, []);
|
||||
|
|
@ -118,12 +121,26 @@ export function ToolsTab({ project }: { project: Project }) {
|
|||
const copy = (c: string) => { navigator.clipboard?.writeText(c).then(() => d.toast({ kind: 'ok', title: t('tools.copied') }), () => {}); };
|
||||
const groups = TOOLBOX.map(g => ({ g, ms: g.methods.map(x => by.get(x)).filter((x): x is MethodDescriptor => !!x) }));
|
||||
const cur = groups.find(x => x.g.id === open);
|
||||
const needle = q.trim().toLowerCase();
|
||||
const hits = needle ? groups.flatMap(x => x.ms).filter(m => [m.method, m.title, m.description ?? ''].some(x => x.toLowerCase().includes(needle))) : [];
|
||||
return (
|
||||
<div className="scr-tools">
|
||||
<div className={`scr-tools${dense ? ' dense' : ''}`}>
|
||||
<p className="tl-lead"><Icon name="info" />{t('tools.lead')}</p>
|
||||
{methods && (
|
||||
<Toolbar label={t('tools.filter')}>
|
||||
<div className="search-input"><Icon name="search" /><input className="input" value={q} onChange={e => setQ(e.target.value)} placeholder={t('tools.search', { count: groups.reduce((a, x) => a + x.ms.length, 0) })} aria-label={t('tools.searchAria')} onKeyDown={e => { if (e.key === 'Escape') setQ(''); }} /></div>
|
||||
<span className="grow" />
|
||||
<Seg value={dense ? 'list' : 'cards'} onChange={v => setDense(v === 'list')} label={t('tools.density')} options={[['cards', t('pf.cards'), 'grid'], ['list', t('tools.list'), 'layers']]} />
|
||||
</Toolbar>
|
||||
)}
|
||||
{needle && (
|
||||
<Section title={t('tools.results', { count: hits.length })} icon="search">
|
||||
{hits.length ? <div className="tl-cmds">{hits.map(m => <Tool key={m.method} m={m} project={project} />)}</div> : <p className="muted">{t('tools.noHits')}</p>}
|
||||
</Section>
|
||||
)}
|
||||
{err && <ErrorNotice error={err} />}
|
||||
{!methods && !err && <Skeleton lines={4} />}
|
||||
{methods && (
|
||||
{methods && !needle && (
|
||||
<div className="tl-groups" role="group" aria-label={t('tab.tools')}>
|
||||
{groups.map(({ g, ms }) => {
|
||||
const isOpen = open === g.id;
|
||||
|
|
@ -138,7 +155,7 @@ export function ToolsTab({ project }: { project: Project }) {
|
|||
})}
|
||||
</div>
|
||||
)}
|
||||
{methods && cur && (
|
||||
{methods && cur && !needle && (
|
||||
<Section id="tools-open" title={t(`tools.g.${cur.g.id}` as Key)} icon="sliders" sub={t('tools.count', { count: cur.ms.length })}>
|
||||
<div className="tl-cmds">{cur.ms.map(m => <Tool key={m.method} m={m} project={project} />)}</div>
|
||||
</Section>
|
||||
|
|
|
|||
|
|
@ -46,3 +46,8 @@
|
|||
.scr-tools .tl-step { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-radius: var(--ds-r); background: var(--ds-inset); border: 1px solid var(--ds-stroke); min-width: 0; }
|
||||
.scr-tools .tl-step-t { display: grid; gap: 4px; flex: 1; min-width: 0; color: var(--ink); font-weight: 600; }
|
||||
.scr-tools .tl-step-t code { font-size: var(--ds-t-micro); color: var(--muted); font-weight: 500; overflow-wrap: anywhere; }
|
||||
/* list layout: smaller group tiles, one command per row */
|
||||
.scr-tools.dense .tl-groups { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 8px; }
|
||||
.scr-tools.dense .tl-group { padding-top: 8px; padding-bottom: 8px; }
|
||||
.scr-tools.dense .tl-group-ic { display: none; }
|
||||
.scr-tools.dense .tl-cmds { grid-template-columns: minmax(0, 1fr); gap: 8px; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue