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>
323 lines
16 KiB
React
323 lines
16 KiB
React
// drover-workshop.jsx — Workshop: industrial slate + amber accent. Subtle texture.
|
|
|
|
const WSH = {
|
|
bg: '#181a1d', chrome: '#121417', panel: '#1f2226', panel2: '#23272c',
|
|
border: '#2c3036', borderSoft: '#222529',
|
|
text: '#e5e3df', dim: '#9a958c', dimmer: '#5e5b56',
|
|
accent: '#e89a3c', accentSoft: 'rgba(232,154,60,0.15)',
|
|
danger: '#d56654', warn: '#d4a248', pass: '#7fb37b', skip: '#7c7872',
|
|
inputBg: '#141619',
|
|
primaryFg: '#1a120a',
|
|
};
|
|
|
|
function WorkshopWindow({ initial }) {
|
|
const t = WSH;
|
|
const D = window.useDrover(initial);
|
|
const palette = { pending: t.dimmer, running: t.accent, passed: t.pass, failed: t.danger, skipped: t.skip };
|
|
const fontUI = '"IBM Plex Sans","Inter",system-ui,sans-serif';
|
|
const fontMono = '"IBM Plex Mono","JetBrains Mono",ui-monospace,monospace';
|
|
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: 13, overflow:'hidden',
|
|
border:'1px solid #000', position:'relative',
|
|
}}>
|
|
{/* subtle grain */}
|
|
<div style={{
|
|
position:'absolute', inset: 0, pointerEvents:'none', opacity: 0.5,
|
|
backgroundImage: `radial-gradient(rgba(255,255,255,0.014) 1px, transparent 1px)`,
|
|
backgroundSize: '3px 3px',
|
|
}}/>
|
|
|
|
{/* title — workshop label tag */}
|
|
<div style={{
|
|
height: 36, background: t.chrome, borderBottom:`1px solid ${t.border}`,
|
|
display:'flex', alignItems:'center', userSelect:'none', position:'relative', zIndex: 1,
|
|
}}>
|
|
<div style={{
|
|
width: 4, alignSelf:'stretch', background: t.accent, marginRight: 12,
|
|
}}/>
|
|
<div style={{ display:'flex', alignItems:'center', gap: 9, flex: 1 }}>
|
|
<span style={{ fontSize: 13, fontWeight: 700, letterSpacing: 1.5, textTransform:'uppercase' }}>Drover-Go</span>
|
|
<span style={{
|
|
fontSize: 10, color: t.dim, fontFamily: fontMono, padding:'1px 5px',
|
|
border:`1px solid ${t.border}`, borderRadius: 1,
|
|
}}>v0.4.2</span>
|
|
</div>
|
|
<div style={{ display:'flex' }}>
|
|
{[<window.IconGear color={t.dim}/>, <window.IconMin color={t.dim}/>, <window.IconClose color={t.dim}/>].map((ic,i) => (
|
|
<div key={i} style={{
|
|
width: 40, height: 36, display:'flex', alignItems:'center', justifyContent:'center', cursor:'pointer',
|
|
borderLeft:`1px solid ${t.borderSoft}`,
|
|
}}>{ic}</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div style={{ flex: 1, overflow:'auto', padding:'14px 16px 4px', position:'relative', zIndex: 1 }}>
|
|
{/* form */}
|
|
<WshHead t={t}>SOCKS5 PROXY</WshHead>
|
|
<div style={{ background: t.panel, padding: 13, borderLeft:`3px solid ${t.accent}`, borderRadius: 1 }}>
|
|
<div style={{ display:'flex', gap: 10 }}>
|
|
<WshField t={t} label="HOST" style={{ flex: 1 }}>
|
|
<WshInput t={t} value={D.form.host} onChange={v => D.update({ host: v })}
|
|
placeholder="95.165.72.59 / example.com" onSubmit={D.runCheck} fontUI={fontUI}/>
|
|
</WshField>
|
|
<WshField t={t} label="PORT" style={{ width: 96 }}>
|
|
<WshInput t={t} value={D.form.port} onChange={v => D.update({ port: v.replace(/\D/g,'') })}
|
|
placeholder="12334" inputMode="numeric" onSubmit={D.runCheck} fontUI={fontUI}/>
|
|
</WshField>
|
|
</div>
|
|
<div style={{ height: 11 }}/>
|
|
<WshSwitch t={t} checked={D.form.auth}
|
|
onChange={v => { D.update({ auth: v }); if (v) setTimeout(()=>document.getElementById('wsh-login')?.focus(),30); }}>
|
|
AUTHENTICATION
|
|
</WshSwitch>
|
|
<div style={{
|
|
display:'flex', gap: 10, marginTop: 10,
|
|
opacity: D.form.auth?1:0.4, pointerEvents: D.form.auth?'auto':'none',
|
|
}}>
|
|
<WshField t={t} label="LOGIN" style={{ flex: 1 }}>
|
|
<WshInput id="wsh-login" t={t} value={D.form.login} disabled={!D.form.auth}
|
|
onChange={v => D.update({ login: v })} placeholder="user" onSubmit={D.runCheck} fontUI={fontUI}/>
|
|
</WshField>
|
|
<WshField t={t} label="PASSWORD" style={{ flex: 1 }}>
|
|
<WshInput t={t} value={D.form.password} type="password" disabled={!D.form.auth}
|
|
onChange={v => D.update({ password: v })} placeholder="••••••" onSubmit={D.runCheck} fontUI={fontUI}/>
|
|
</WshField>
|
|
</div>
|
|
<div style={{ height: 12 }}/>
|
|
<button onClick={D.runCheck} disabled={D.phase==='checking'||isActive} style={{
|
|
width:'100%', padding:'9px 14px', borderRadius: 1, border:'none',
|
|
background: (D.phase==='checking'||isActive) ? t.panel2 : t.accent,
|
|
color: (D.phase==='checking'||isActive) ? t.dimmer : t.primaryFg,
|
|
fontWeight: 700, fontSize: 12, letterSpacing: 1.5, textTransform:'uppercase',
|
|
cursor: D.phase==='checking'?'not-allowed':'pointer', fontFamily: fontUI,
|
|
}}>{D.phase==='checking' ? 'Checking…' : 'Check connection'}</button>
|
|
</div>
|
|
|
|
<div style={{ height: 14 }}/>
|
|
<WshHead t={t} right={
|
|
D.phase==='checking' ? `${Object.keys(D.results).length}/${D.tests.length}` :
|
|
D.lastSummary ? (D.lastSummary.failed === 0 ? 'all passed' : `${D.lastSummary.failed} failed`) : null
|
|
}>STATUS</WshHead>
|
|
<div style={{ background: t.panel, padding: 13, borderRadius: 1 }}>
|
|
{D.phase === 'idle'
|
|
? <div style={{ display:'flex', alignItems:'center', gap: 8, color: t.dim }}>
|
|
<span style={{ width: 7, height: 7, background: t.dimmer }}/>
|
|
Ready to check
|
|
</div>
|
|
: <WshStatus t={t} D={D} palette={palette} fontMono={fontMono}/>}
|
|
</div>
|
|
|
|
<div style={{ height: 14 }}/>
|
|
<div style={{ background: t.panel, padding: 13, borderRadius: 1 }}>
|
|
<div style={{ display:'flex', gap: 10 }}>
|
|
<WshStartBtn t={t} D={D} fontUI={fontUI}/>
|
|
<WshStopBtn t={t} D={D} fontUI={fontUI}/>
|
|
</div>
|
|
{isActive && (
|
|
<div style={{
|
|
marginTop: 12, paddingTop: 12, borderTop:`1px solid ${t.border}`,
|
|
display:'flex', justifyContent:'space-between', color: t.dim,
|
|
}}>
|
|
<Stat val={window.fmtBytes(D.stats.up)} lbl="↑" t={t} fontMono={fontMono} hl={t.pass}/>
|
|
<Stat val={window.fmtBytes(D.stats.down)} lbl="↓" t={t} fontMono={fontMono} hl={t.accent}/>
|
|
<Stat val={D.stats.tcp} lbl="tcp" t={t} fontMono={fontMono}/>
|
|
<Stat val={D.stats.udp} lbl="udp" t={t} fontMono={fontMono}/>
|
|
<Stat val={window.fmtUptime(D.stats.uptimeS)} lbl="up" t={t} fontMono={fontMono}/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div style={{ height: 12 }}/>
|
|
</div>
|
|
<WshLogs t={t} D={D} fontMono={fontMono}/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function WshHead({ t, children, right }) {
|
|
return (
|
|
<div style={{ display:'flex', alignItems:'baseline', marginBottom: 8 }}>
|
|
<div style={{
|
|
fontSize: 10.5, fontWeight: 700, letterSpacing: 2, color: t.dim,
|
|
display:'flex', alignItems:'center', gap: 8,
|
|
}}>
|
|
<span style={{ width: 8, height: 2, background: t.accent }}/>
|
|
{children}
|
|
</div>
|
|
{right && <div style={{ marginLeft:'auto', fontSize: 10.5, color: t.dimmer, fontFamily:'"IBM Plex Mono",monospace', letterSpacing: 0.5 }}>{right}</div>}
|
|
</div>
|
|
);
|
|
}
|
|
function WshField({ t, label, children, style }) {
|
|
return <label style={{ display:'flex', flexDirection:'column', gap: 5, ...style }}>
|
|
<span style={{ fontSize: 10, color: t.dim, letterSpacing: 1.5, fontWeight: 600 }}>{label}</span>{children}
|
|
</label>;
|
|
}
|
|
function WshInput({ t, value, onChange, type, placeholder, onSubmit, disabled, id, fontUI, inputMode }) {
|
|
return <input id={id} value={value} type={type||'text'} disabled={disabled} inputMode={inputMode}
|
|
onChange={e => onChange(e.target.value)} placeholder={placeholder}
|
|
onKeyDown={e => e.key === 'Enter' && onSubmit?.()}
|
|
style={{
|
|
background: t.inputBg, color: disabled ? t.dimmer : t.text,
|
|
border:`1px solid ${t.border}`, borderRadius: 1, padding:'8px 10px',
|
|
fontSize: 13, fontFamily: fontUI, outline:'none', width:'100%', boxSizing:'border-box',
|
|
}}/>;
|
|
}
|
|
function WshSwitch({ t, checked, onChange, children }) {
|
|
return (
|
|
<label style={{ display:'inline-flex', alignItems:'center', gap: 9, cursor:'pointer' }}>
|
|
<span style={{
|
|
width: 32, height: 18, padding: 2, boxSizing:'border-box',
|
|
background: checked ? t.accent : t.border,
|
|
display:'flex', alignItems:'center', transition:'background .14s', borderRadius: 1,
|
|
}}>
|
|
<span style={{
|
|
width: 12, height: 12, background: checked ? '#1a120a' : '#9a958c',
|
|
transform: checked ? 'translateX(14px)' : 'translateX(0)', transition:'transform .14s',
|
|
}}/>
|
|
</span>
|
|
<input type="checkbox" checked={checked} onChange={e => onChange(e.target.checked)} style={{ display:'none' }}/>
|
|
<span style={{ fontSize: 11, fontWeight: 600, letterSpacing: 1.2 }}>{children}</span>
|
|
</label>
|
|
);
|
|
}
|
|
function WshStatus({ t, D, palette, fontMono }) {
|
|
return (
|
|
<div>
|
|
{D.phase === 'checking'
|
|
? <div style={{ display:'flex', alignItems:'center', gap: 8, marginBottom: 8 }}>
|
|
<window.StatusDot state="running" palette={palette} size={14}/>
|
|
<span style={{ fontWeight: 500 }}>Running diagnostics…</span>
|
|
</div>
|
|
: <div style={{
|
|
padding:'8px 11px', marginBottom: 10, fontSize: 12, fontWeight: 600, letterSpacing: 0.5,
|
|
background: D.lastSummary?.failed === 0 ? `${t.pass}1f` : t.accentSoft,
|
|
borderLeft: `3px solid ${D.lastSummary?.failed === 0 ? t.pass : t.accent}`,
|
|
color: D.lastSummary?.failed === 0 ? t.pass : t.accent,
|
|
}}>
|
|
{D.lastSummary?.failed === 0
|
|
? 'All checks passed. Ready to start.'
|
|
: `${D.lastSummary?.failed} of ${D.tests.length} checks failed. Some features won't work.`}
|
|
</div>}
|
|
<div>
|
|
{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} style={{ borderBottom:`1px solid ${t.borderSoft}` }}>
|
|
<div style={{ display:'flex', alignItems:'center', gap: 10, height: 28 }}>
|
|
<window.StatusDot state={state} palette={palette} size={13}/>
|
|
<span style={{ color: state==='pending'?t.dim:t.text, fontSize: 12.5 }} title={test.desc}>{test.label}</span>
|
|
<span style={{
|
|
marginLeft:'auto', fontFamily: fontMono, fontSize: 11,
|
|
color: state==='failed'?t.danger:state==='skipped'?t.skip:t.dim,
|
|
}}>{r?.metric}</span>
|
|
{r?.result === 'failed' && (
|
|
<button onClick={() => D.toggleExpand(test.id)} style={{
|
|
background:'transparent', border:'none', cursor:'pointer', padding: 4, 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: '0 0 6px 23px', padding:'7px 10px',
|
|
background: t.panel2, borderLeft:`2px solid ${t.danger}`, fontSize: 12,
|
|
}}>
|
|
<div style={{ color: t.danger, fontWeight: 600, marginBottom: 3 }}>{r.error}</div>
|
|
<div style={{ color: t.dim, lineHeight: 1.5 }}>{r.hint}</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
function WshStartBtn({ t, D, fontUI }) {
|
|
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:'9px 14px', borderRadius: 1, fontWeight: 700, fontSize: 12, letterSpacing: 1.5,
|
|
background: `${c}1f`, color: c, borderLeft:`3px solid ${c}`,
|
|
textTransform:'uppercase', display:'flex', alignItems:'center', justifyContent:'center', gap: 8,
|
|
}}>
|
|
<span className="drv-pulsedot" style={{ width: 8, height: 8, background: c }}/>
|
|
Active{warning ? ' · UDP warn' : ''}
|
|
</div>
|
|
);
|
|
}
|
|
return <button onClick={D.startProxy} disabled={!ok} style={{
|
|
flex: 1, padding:'9px 14px', borderRadius: 1, border:'none',
|
|
background: ok ? t.accent : t.panel2, color: ok ? t.primaryFg : t.dimmer,
|
|
fontWeight: 700, fontSize: 12, letterSpacing: 1.5, textTransform:'uppercase',
|
|
cursor: ok?'pointer':'not-allowed', fontFamily: fontUI,
|
|
}}>Start proxying</button>;
|
|
}
|
|
function WshStopBtn({ t, D, fontUI }) {
|
|
const enabled = D.phase === 'active';
|
|
return <button onClick={D.stopProxy} disabled={!enabled} style={{
|
|
flex: 1, padding:'9px 14px', borderRadius: 1, background:'transparent',
|
|
color: enabled ? t.text : t.dimmer, border:`1px solid ${t.border}`,
|
|
fontWeight: 700, fontSize: 12, letterSpacing: 1.5, textTransform:'uppercase',
|
|
cursor: enabled?'pointer':'not-allowed', fontFamily: fontUI,
|
|
}}>Stop</button>;
|
|
}
|
|
function Stat({ val, lbl, t, fontMono, hl }) {
|
|
return <div style={{ display:'flex', alignItems:'baseline', gap: 4 }}>
|
|
<span style={{ fontSize: 10, color: hl||t.dimmer, textTransform:'uppercase', letterSpacing: 0.5, fontWeight: 700 }}>{lbl}</span>
|
|
<span style={{ fontFamily: fontMono, fontSize: 11.5, color: t.text }}>{val}</span>
|
|
</div>;
|
|
}
|
|
function WshLogs({ t, D, fontMono }) {
|
|
return (
|
|
<div style={{ borderTop:`1px solid ${t.border}`, background: t.chrome, flexShrink: 0, position:'relative', zIndex: 1 }}>
|
|
<button onClick={() => D.setLogsOpen(!D.logsOpen)} style={{
|
|
width:'100%', padding:'9px 16px', display:'flex', alignItems:'center', gap: 10,
|
|
background:'transparent', border:'none', color: t.dim, cursor:'pointer', fontSize: 11,
|
|
letterSpacing: 1.5, textTransform:'uppercase',
|
|
}}>
|
|
<window.IconChevron color={t.dim} dir={D.logsOpen?'down':'right'}/>
|
|
<span style={{ fontWeight: 700 }}>Logs</span>
|
|
<span style={{ marginLeft:'auto', fontFamily: fontMono, fontSize: 10.5, color: t.dimmer, letterSpacing: 0 }}>{D.logs.length} lines</span>
|
|
</button>
|
|
{D.logsOpen && (
|
|
<>
|
|
<div style={{ display:'flex', gap: 6, padding:'0 16px 8px' }}>
|
|
{[['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}`, borderRadius: 1,
|
|
padding:'3px 8px', fontSize: 10.5, color: t.dim, cursor:'pointer',
|
|
fontFamily: fontMono, letterSpacing: 1, textTransform:'uppercase',
|
|
}}>{l}</button>
|
|
))}
|
|
</div>
|
|
<div className="drv-log" ref={el => el && (el.scrollTop = el.scrollHeight)}
|
|
style={{ maxHeight: 130, overflowY:'auto', padding:'8px 16px',
|
|
fontFamily: fontMono, fontSize: 10.5, lineHeight: 1.6, 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.accent, fontWeight: 700 }}>[{l.level}]</span>{' '}
|
|
{l.msg}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
window.WorkshopWindow = WorkshopWindow;
|