Desktop v2 screens: releases (store cards, stage stepper, rollout ring), submit checklist, TestFlight cards; listings as store pages with completeness, framed screenshots with issue badges, product cards, price matrix with gap shading; health tiles and issue cards, store-style review feed with rating distribution, growth funnel, readiness score with fix-first, tools grid, store-info sheet; settings, accounts, folders, dependencies and onboarding in the v2 language
This commit is contained in:
parent
ce939ee063
commit
f131de9df2
47 changed files with 2309 additions and 927 deletions
|
|
@ -8,7 +8,9 @@ import { bytes } from '../lib/format';
|
|||
import { shortPath } from '../paths';
|
||||
import { useData } from '../store';
|
||||
import { Icon, type IconName } from '../ui/icons';
|
||||
import { Card, Chip, Notice, Skeleton } from '../ui/kit';
|
||||
import { Chip, Notice, Skeleton } from '../ui/kit';
|
||||
import { PageHeader, Section, StatTile, StatusPill } from '../ui/ds';
|
||||
import '../styles/deps.css';
|
||||
|
||||
const TOOL_ICON: Record<string, IconName> = { ffmpeg: 'image', ffprobe: 'search', chrome: 'globe', xcode: 'terminal', simctl: 'phone', adb: 'phone', git: 'layers' };
|
||||
|
||||
|
|
@ -72,29 +74,32 @@ export function Dependencies() {
|
|||
const managed = (id: string): DownloadInfo | undefined => info.find(i => i.tool === (id === 'ffprobe' ? 'ffmpeg' : id));
|
||||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="ph">
|
||||
<div><h1>{t('nav.dependencies')}</h1><p>{t('deps.lead')}</p></div>
|
||||
<div className="actions"><button className="btn" onClick={refresh} disabled={busy}><Icon name="refresh" className={busy ? 'spin' : undefined} />{t('deps.checkAgain')}</button></div>
|
||||
</div>
|
||||
<div className="page scr-deps">
|
||||
<PageHeader icon="package" eyebrow={t('v2cfg.workspace')} title={t('nav.dependencies')} subtitle={t('deps.lead')} actions={<><button className="btn" onClick={refresh} disabled={busy}><Icon name="refresh" className={busy ? 'spin' : undefined} />{t('deps.checkAgain')}</button></>} />
|
||||
{error && <Notice tone="bad">{error}</Notice>}
|
||||
<Card title={t('deps.tools')} icon="package" sub={tools ? t('deps.found', { found, total: tools.length }) : t('common.loading')} pad={false}>
|
||||
{tools && <div className="ds-grid deps-kpis">
|
||||
<StatTile icon="check" tone={found === tools.length ? 'ok' : undefined} label={t('v2cfg.depsFound')} value={`${found} / ${tools.length}`} />
|
||||
<StatTile icon="alert" tone={tools.some(x => !x.found || x.status === 'outdated') ? 'warn' : undefined} label={t('v2cfg.depsNeeds')} value={tools.filter(x => !x.found || x.status === 'outdated').length} />
|
||||
<StatTile icon="download" label={t('v2cfg.depsInApp')} value={info.filter(i => i.installed).length} sub={t('v2cfg.depsInAppSub')} />
|
||||
</div>}
|
||||
<Section title={t('deps.tools')} icon="package" sub={tools ? t('deps.found', { found, total: tools.length }) : t('common.loading')} pad={false}>
|
||||
{!tools ? <div className="card-body"><Skeleton lines={6} /></div> : (
|
||||
<div className="list">
|
||||
<div className="list deps-grid">
|
||||
{tools.map(x => {
|
||||
const st = toolState(x);
|
||||
const m = x.install.kind === 'download' ? managed(x.id) : undefined;
|
||||
const running = m ? dl[m.tool] : undefined;
|
||||
const pct = running?.total ? running.received / running.total : null;
|
||||
return (
|
||||
<div className="li tool-row" key={x.id} style={{ alignItems: 'center' }}>
|
||||
<div className={`li tool-row tool-${st.tone}`} key={x.id}>
|
||||
<span className="ti"><Icon name={TOOL_ICON[x.id] ?? 'package'} /></span>
|
||||
<div className="li-main">
|
||||
<span className="row" style={{ gap: 8 }}><span className="li-title">{x.name}</span><Chip tone={st.tone}>{st.text}</Chip>{x.version && <span className="small muted num">{x.version}</span>}</span>
|
||||
<span className="row" style={{ gap: 8 }}><span className="li-title">{x.name}</span></span>
|
||||
<span className="row tool-chips" style={{ gap: 6 }}><StatusPill tone={st.tone === 'accent' ? 'info' : st.tone}>{st.text}</StatusPill>{x.version && <Chip tone="outline">{x.version}</Chip>}{x.required && <Chip tone="neutral">{t('v2cfg.needs', { version: x.required })}</Chip>}</span>
|
||||
<span className="li-sub">{x.usedFor}</span>
|
||||
{x.path && <span className="li-sub mono-id selectable" title={x.path}>{shortPath(x.path, d.info?.home)}</span>}
|
||||
</div>
|
||||
<div className="actions" style={{ flexWrap: 'nowrap' }}>
|
||||
<div className="actions tool-acts">
|
||||
{running ? (
|
||||
<div className="prog" aria-live="polite">
|
||||
<div className={`bar${pct === null ? ' indet' : ''}`}><span style={{ width: `${(pct ?? 0.35) * 100}%` }} /></div>
|
||||
|
|
@ -114,9 +119,9 @@ export function Dependencies() {
|
|||
})}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</Section>
|
||||
{sdk && (
|
||||
<Card title={t('deps.sdkTitle')} icon="phone" sub={t('deps.sdkSub')} id="android-sdk"
|
||||
<Section title={t('deps.sdkTitle')} icon="phone" sub={t('deps.sdkSub')} id="android-sdk"
|
||||
actions={sdk.path ? <Chip tone="ok">{t('deps.installed')}</Chip> : <><Chip tone="warn">{t('deps.missing')}</Chip><button className="btn btn-sm" onClick={() => open('https://developer.android.com/studio')}><Icon name="external" />{t('deps.getIt')}</button></>}>
|
||||
{sdk.path ? (
|
||||
<dl className="kv">
|
||||
|
|
@ -129,27 +134,27 @@ export function Dependencies() {
|
|||
<ul className="note-list small">{sdk.checked.map((c, i) => <li key={i} className="selectable">{d.info?.home ? c.split(d.info.home).join("~") : c}</li>)}</ul>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</Section>
|
||||
)}
|
||||
<div className="grid g2">
|
||||
<div className="grid g2 deps-src">
|
||||
{info.map(m => (
|
||||
<Card key={m.tool} title={m.tool === 'chrome' ? 'Chrome for Testing' : `ffmpeg ${m.version ?? ''}`} icon={m.tool === 'chrome' ? 'globe' : 'image'} sub={m.installed ? t('deps.inAppData', { folder: m.folder }) : t('deps.notDownloaded')}>
|
||||
<Section key={m.tool} title={m.tool === 'chrome' ? 'Chrome for Testing' : `ffmpeg ${m.version ?? ''}`} icon={m.tool === 'chrome' ? 'globe' : 'image'} sub={m.installed ? t('deps.inAppData', { folder: m.folder }) : t('deps.notDownloaded')}>
|
||||
<dl className="kv">
|
||||
<dt>{t('deps.source')}</dt><dd className="selectable small">{m.source}</dd>
|
||||
<dt>{t('deps.license')}</dt><dd className="small">{m.license}</dd>
|
||||
<dt>{t('deps.verify')}</dt><dd className="small">{m.tool === 'chrome' ? t('deps.verifyChrome') : t('deps.verifyFfmpeg')}</dd>
|
||||
</dl>
|
||||
<div className="actions" style={{ marginTop: 12 }}><button className="btn btn-sm btn-ghost" onClick={() => open(m.licenseUrl)}><Icon name="external" />{t('deps.licenseLink')}</button></div>
|
||||
</Card>
|
||||
</Section>
|
||||
))}
|
||||
</div>
|
||||
<Card title={t('deps.terminal')} icon="terminal" sub={t('deps.terminalSub')}>
|
||||
<Section title={t('deps.terminal')} icon="terminal" sub={t('deps.terminalSub')}>
|
||||
<div className="stack">
|
||||
{['storebound doctor', 'bun add -d playwright', 'brew install ffmpeg'].map(c => (
|
||||
<div className="cmd" key={c}><code className="selectable">{c}</code><button className="btn btn-sm btn-ghost btn-icon" onClick={() => copy(c)} aria-label={t('deps.copy', { cmd: c })}><Icon name="copy" /></button></div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue