113616b039
Release / release (push) Successful in 3m19s
Stash the full claude.ai/design output (12 JSX variants — brutalist, classic, cli, compact, fluent-live, glass, hero-live, minimal, sketches, studio, wizard-live, workshop — plus shared hooks and a standalone HTML preview) for reference when we get to the Wails frontend in Phase 6/7. Source archive: C:\Users\root\Downloads\app(1).zip (~1MB). Not wired into any build target yet — current GUI is the temporary MessageBox stub. Pulling these in is the goal of the Wails phase. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
294 lines
14 KiB
React
294 lines
14 KiB
React
// drover-v2.jsx — Round 2: four new dark-only variants.
|
||
// Reuses window.useDrover / StatusDot / icons / fmt helpers from drover-shared.jsx.
|
||
|
||
// =============================================================================
|
||
// V5 — COMPACT PRO. High-density, table-like. Wireshark/HTOP feel. Mono numbers.
|
||
// =============================================================================
|
||
const CPT = {
|
||
bg: '#101216', chrome: '#0a0c0f', panel: '#15181d', panel2: '#1a1d23',
|
||
border: '#262a31', borderSoft: '#1d2026',
|
||
text: '#e1e3e8', dim: '#838892', dimmer: '#535862',
|
||
accent: '#7aa9ff', danger: '#e5685a', warn: '#d6a64a', pass: '#5fc888', skip: '#6a6f78',
|
||
inputBg: '#0c0e12', primaryFg: '#0b1426',
|
||
};
|
||
|
||
function CompactWindow({ initial }) {
|
||
const t = CPT;
|
||
const D = window.useDrover(initial);
|
||
const palette = { pending: t.dimmer, running: t.accent, passed: t.pass, failed: t.danger, skipped: t.skip };
|
||
const fontMono = '"JetBrains Mono","SF Mono",ui-monospace,Consolas,monospace';
|
||
const fontUI = '"Inter","Segoe UI",system-ui,sans-serif';
|
||
const isActive = D.phase === 'active';
|
||
|
||
return (
|
||
<div style={{
|
||
width: 480, height: 640, background: t.bg, color: t.text, display:'flex', flexDirection:'column',
|
||
fontFamily: fontUI, fontSize: 12, overflow: 'hidden',
|
||
border: '1px solid #000',
|
||
}}>
|
||
{/* title bar */}
|
||
<div style={{
|
||
height: 28, background: t.chrome, borderBottom:`1px solid ${t.border}`,
|
||
display:'flex', alignItems:'center', userSelect:'none',
|
||
}}>
|
||
<div style={{ display:'flex', alignItems:'center', gap: 7, padding:'0 10px', flex:1 }}>
|
||
<window.BrandMark size={12} color={t.accent}/>
|
||
<span style={{ fontSize: 11.5, fontWeight: 600 }}>drover-go</span>
|
||
<span style={{ fontSize: 10, color: t.dimmer, fontFamily: fontMono }}>0.4.2</span>
|
||
<span style={{ fontSize: 10, color: t.dim, marginLeft: 8, fontFamily: fontMono }}>
|
||
· {D.form.host}:{D.form.port}{D.form.auth ? ' · auth' : ''}
|
||
</span>
|
||
</div>
|
||
<div style={{ display:'flex' }}>
|
||
<CompactCell t={t}><window.IconGear color={t.dim}/></CompactCell>
|
||
<CompactCell t={t}><window.IconMin color={t.dim}/></CompactCell>
|
||
<CompactCell t={t} hover="#c0463f"><window.IconClose color={t.dim}/></CompactCell>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ flex: 1, overflow:'auto' }}>
|
||
{/* Row 1: form, single tight line */}
|
||
<div style={{ padding: '10px 12px', borderBottom:`1px solid ${t.borderSoft}` }}>
|
||
<div style={cptHead(t)}>SOCKS5</div>
|
||
<div style={{ display:'flex', gap: 6, alignItems:'center' }}>
|
||
<CptInput t={t} fontMono={fontMono} value={D.form.host}
|
||
onChange={v => D.update({ host: v })} placeholder="host" style={{ flex: 1 }}
|
||
onSubmit={D.runCheck}/>
|
||
<span style={{ color: t.dimmer, fontFamily: fontMono }}>:</span>
|
||
<CptInput t={t} fontMono={fontMono} value={D.form.port}
|
||
onChange={v => D.update({ port: v.replace(/\D/g,'') })}
|
||
placeholder="port" style={{ width: 64 }} onSubmit={D.runCheck}/>
|
||
<CptCheck t={t} checked={D.form.auth} onChange={(v) => {
|
||
D.update({ auth: v }); if (v) setTimeout(()=>document.getElementById('cpt-login')?.focus(),30);
|
||
}}>auth</CptCheck>
|
||
</div>
|
||
{D.form.auth && (
|
||
<div style={{ display:'flex', gap: 6, marginTop: 6 }}>
|
||
<CptInput id="cpt-login" t={t} fontMono={fontMono} value={D.form.login}
|
||
onChange={v => D.update({ login: v })} placeholder="login"
|
||
style={{ flex: 1 }} onSubmit={D.runCheck}/>
|
||
<CptInput t={t} fontMono={fontMono} value={D.form.password} type="password"
|
||
onChange={v => D.update({ password: v })} placeholder="password"
|
||
style={{ flex: 1 }} onSubmit={D.runCheck}/>
|
||
</div>
|
||
)}
|
||
<button onClick={D.runCheck} disabled={D.phase === 'checking' || isActive} style={{
|
||
marginTop: 8, width:'100%', padding:'6px 10px', borderRadius: 2, fontFamily: fontMono,
|
||
background: (D.phase==='checking'||isActive)?t.panel2:t.accent,
|
||
color: (D.phase==='checking'||isActive)?t.dimmer:t.primaryFg,
|
||
border:`1px solid ${t.border}`, fontWeight: 700, fontSize: 11.5, letterSpacing: 0.5,
|
||
cursor: D.phase==='checking'?'not-allowed':'pointer', textAlign: 'left',
|
||
}}>
|
||
{'>>'} {D.phase==='checking'?'CHECKING…':'check connection'}
|
||
</button>
|
||
</div>
|
||
|
||
{/* Row 2: status table, full width */}
|
||
<div style={{ padding: '8px 12px', borderBottom:`1px solid ${t.borderSoft}` }}>
|
||
<div style={cptHead(t)}>
|
||
<span>STATUS</span>
|
||
<span style={{ marginLeft:'auto', color: t.dim, fontFamily: fontMono }}>
|
||
{D.phase === 'idle' && 'idle'}
|
||
{D.phase === 'checking' && `${Object.keys(D.results).length}/${D.tests.length}`}
|
||
{(D.phase === 'checked' || D.phase === 'active') &&
|
||
(D.lastSummary?.failed === 0 ? 'all-pass' : `${D.lastSummary?.failed}/${D.tests.length} fail`)}
|
||
</span>
|
||
</div>
|
||
{D.phase === 'idle'
|
||
? <div style={{ color: t.dim, fontFamily: fontMono, fontSize: 11 }}>
|
||
<span style={{ color: t.dimmer }}>›</span> ready to check_
|
||
</div>
|
||
: <CompactStatusTable t={t} D={D} palette={palette} fontMono={fontMono}/>}
|
||
</div>
|
||
|
||
{/* Row 3: actions */}
|
||
<div style={{ padding: '8px 12px' }}>
|
||
<div style={{ display:'flex', gap: 6 }}>
|
||
<CompactStartBtn t={t} D={D} fontMono={fontMono}/>
|
||
<CompactStopBtn t={t} D={D} fontMono={fontMono}/>
|
||
</div>
|
||
{isActive && (
|
||
<div style={{
|
||
marginTop: 8, padding: '5px 8px', background: t.panel,
|
||
border:`1px solid ${t.borderSoft}`, borderRadius: 2,
|
||
fontFamily: fontMono, fontSize: 10.5, color: t.dim,
|
||
display:'flex', justifyContent:'space-between',
|
||
}}>
|
||
<span><window.IconArrowUp color={t.pass}/> {window.fmtBytes(D.stats.up)}</span>
|
||
<span><window.IconArrowDown color={t.accent}/> {window.fmtBytes(D.stats.down)}</span>
|
||
<span>tcp:{D.stats.tcp}</span>
|
||
<span>udp:{D.stats.udp}</span>
|
||
<span>up:{window.fmtUptime(D.stats.uptimeS)}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Logs */}
|
||
<CompactLogs t={t} D={D} fontMono={fontMono}/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function CompactCell({ children, t, hover }) {
|
||
const [h, setH] = React.useState(false);
|
||
return <div onMouseEnter={()=>setH(true)} onMouseLeave={()=>setH(false)} style={{
|
||
width: 32, height: 28, display:'flex', alignItems:'center', justifyContent:'center',
|
||
cursor:'pointer', background: h && hover ? hover : 'transparent', transition:'background .1s',
|
||
}}>{children}</div>;
|
||
}
|
||
function cptHead(t) {
|
||
return {
|
||
fontSize: 9.5, letterSpacing: 1.5, color: t.dim, fontWeight: 700,
|
||
marginBottom: 6, display:'flex', alignItems:'center',
|
||
};
|
||
}
|
||
function CptInput({ value, onChange, placeholder, type, style, onSubmit, t, fontMono, id }) {
|
||
return <input id={id} value={value} type={type||'text'}
|
||
onChange={e => onChange(e.target.value)} placeholder={placeholder}
|
||
onKeyDown={e => e.key === 'Enter' && onSubmit?.()}
|
||
style={{
|
||
background: t.inputBg, color: t.text, border:`1px solid ${t.border}`,
|
||
borderRadius: 2, padding:'5px 7px', fontFamily: fontMono, fontSize: 11.5,
|
||
outline:'none', boxSizing:'border-box', ...style,
|
||
}}/>;
|
||
}
|
||
function CptCheck({ checked, onChange, t, children }) {
|
||
return (
|
||
<label style={{
|
||
display:'inline-flex', alignItems:'center', gap: 5, cursor:'pointer', userSelect:'none',
|
||
padding:'4px 7px', border:`1px solid ${checked?t.accent:t.border}`, borderRadius: 2,
|
||
fontFamily: '"JetBrains Mono",monospace', fontSize: 11,
|
||
background: checked ? `${t.accent}22` : 'transparent', color: checked ? t.accent : t.dim,
|
||
}}>
|
||
<span>[{checked ? 'x' : ' '}]</span>
|
||
<input type="checkbox" checked={checked} onChange={e => onChange(e.target.checked)} style={{display:'none'}}/>
|
||
<span>{children}</span>
|
||
</label>
|
||
);
|
||
}
|
||
function CompactStatusTable({ t, D, palette, fontMono }) {
|
||
return (
|
||
<div style={{ fontFamily: fontMono, fontSize: 11 }}>
|
||
{D.tests.map((test) => {
|
||
const r = D.results[test.id];
|
||
const state = r?.result || (D.running === test.id ? 'running' : 'pending');
|
||
return (
|
||
<div key={test.id}>
|
||
<div style={{
|
||
display:'flex', alignItems:'center', gap: 7, padding:'2px 0', height: 20,
|
||
color: state === 'pending' ? t.dimmer : t.text,
|
||
}}>
|
||
<span style={{ width: 10, color: t.dimmer }}>{
|
||
state==='passed'?'✓':state==='failed'?'✗':state==='skipped'?'–':state==='running'?'›':'·'
|
||
}</span>
|
||
<window.StatusDot state={state} palette={palette} size={10}/>
|
||
<span title={test.desc}>{test.label}</span>
|
||
<span style={{
|
||
marginLeft:'auto',
|
||
color: state==='failed'?t.danger:state==='skipped'?t.skip:state==='passed'?t.pass:t.dim,
|
||
}}>{r?.metric || (state==='running'?'…':'')}</span>
|
||
{r?.result === 'failed' && (
|
||
<button onClick={() => D.toggleExpand(test.id)} style={{
|
||
background:'transparent', border:'none', cursor:'pointer', padding: 2, color: t.dim,
|
||
}}><window.IconChevron color={t.dim} dir={r.expanded?'up':'down'}/></button>
|
||
)}
|
||
</div>
|
||
{r?.result === 'failed' && r.expanded && (
|
||
<div className="drv-fadein" style={{
|
||
margin: '2px 0 6px 17px', padding:'5px 8px',
|
||
background: t.panel2, borderLeft:`2px solid ${t.danger}`,
|
||
fontSize: 10.5, color: t.dim,
|
||
}}>
|
||
<div style={{ color: t.danger, fontWeight: 600, marginBottom: 2 }}>{r.error}</div>
|
||
<div>{r.hint}</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
);
|
||
}
|
||
function CompactStartBtn({ t, D, fontMono }) {
|
||
const allFailed = D.lastSummary && D.lastSummary.failed === D.tests.length;
|
||
const ok = D.phase === 'checked' && !allFailed;
|
||
const active = D.phase === 'active';
|
||
const warning = active && (D.lastSummary?.failed || 0) > 0;
|
||
if (active) {
|
||
const c = warning ? t.warn : t.pass;
|
||
return (
|
||
<div style={{
|
||
flex:1, padding:'6px 10px', border:`1px solid ${c}`, borderRadius: 2,
|
||
background: `${c}1a`, color: c, fontFamily: fontMono, fontWeight: 700, fontSize: 11.5,
|
||
display:'flex', alignItems:'center', gap: 6,
|
||
}}>
|
||
<span className="drv-pulsedot" style={{ width: 6, height: 6, borderRadius: 3, background: c }}/>
|
||
ACTIVE{warning ? ' · UDP-FALLBACK' : ''}
|
||
</div>
|
||
);
|
||
}
|
||
return (
|
||
<button onClick={D.startProxy} disabled={!ok} style={{
|
||
flex: 1, padding:'6px 10px', borderRadius: 2,
|
||
background: ok ? t.accent : t.panel, color: ok ? t.primaryFg : t.dimmer,
|
||
border:`1px solid ${ok ? t.accent : t.border}`, fontFamily: fontMono,
|
||
fontWeight: 700, fontSize: 11.5, letterSpacing: 0.5, textAlign:'left',
|
||
cursor: ok ? 'pointer' : 'not-allowed',
|
||
}}>{'>'} start proxying</button>
|
||
);
|
||
}
|
||
function CompactStopBtn({ t, D, fontMono }) {
|
||
const enabled = D.phase === 'active';
|
||
return (
|
||
<button onClick={D.stopProxy} disabled={!enabled} style={{
|
||
flex: 1, padding:'6px 10px', borderRadius: 2,
|
||
background: t.panel, color: enabled ? t.text : t.dimmer,
|
||
border:`1px solid ${t.border}`, fontFamily: fontMono,
|
||
fontWeight: 700, fontSize: 11.5, letterSpacing: 0.5, textAlign:'left',
|
||
cursor: enabled ? 'pointer' : 'not-allowed',
|
||
}}>{'■'} stop</button>
|
||
);
|
||
}
|
||
function CompactLogs({ t, D, fontMono }) {
|
||
return (
|
||
<div style={{ borderTop:`1px solid ${t.border}`, background: t.chrome, flexShrink: 0 }}>
|
||
<button onClick={() => D.setLogsOpen(!D.logsOpen)} style={{
|
||
width:'100%', padding:'6px 12px', display:'flex', alignItems:'center', gap: 7,
|
||
background:'transparent', border:'none', color: t.dim, cursor:'pointer',
|
||
fontFamily: fontMono, fontSize: 10.5, letterSpacing: 1,
|
||
}}>
|
||
<window.IconChevron color={t.dim} dir={D.logsOpen?'down':'right'}/>
|
||
<span style={{ fontWeight: 700 }}>LOGS</span>
|
||
<span style={{ marginLeft:'auto', color: t.dimmer }}>{D.logs.length} lines</span>
|
||
</button>
|
||
{D.logsOpen && (
|
||
<>
|
||
<div style={{ display:'flex', gap: 4, padding:'0 12px 6px' }}>
|
||
{[['copy', () => navigator.clipboard?.writeText(D.logs.map(x=>`[${x.level}] ${x.msg}`).join('\n'))],
|
||
['clear', D.clearLogs], ['file', null]].map(([l, fn]) => (
|
||
<button key={l} onClick={fn||undefined} style={{
|
||
background:'transparent', border:`1px solid ${t.border}`, color: t.dim,
|
||
padding:'2px 7px', fontSize: 10, fontFamily: fontMono, cursor:'pointer', borderRadius: 2,
|
||
}}>{l}</button>
|
||
))}
|
||
</div>
|
||
<div className="drv-log" ref={el => el && (el.scrollTop = el.scrollHeight)}
|
||
style={{ maxHeight: 110, overflowY:'auto', padding:'5px 12px',
|
||
fontFamily: fontMono, fontSize: 10, lineHeight: 1.55, color: t.dim, background: t.panel }}>
|
||
{D.logs.map((l,i) => (
|
||
<div key={i}>
|
||
<span style={{ color: t.dimmer }}>{window.fmtTime(l.t)}</span>{' '}
|
||
<span style={{ color: l.level==='ERROR'?t.danger:l.level==='WARN'?t.warn:t.pass, fontWeight: 700 }}>{l.level.padEnd(5)}</span>{' '}
|
||
{l.msg}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
window.CompactWindow = CompactWindow;
|