memlnaut-nisps/docs/redesign/manifold-export/ui_kits/memlnaut/console/Drawers.jsx

224 lines
12 KiB
React
Raw Permalink Normal View History

/* Console 2.0 Drawer contents. Each renderer takes (ctx, depth) where depth
is 'peek' | 'expand' | 'full'; what shows is gated by depth (the schema-tier
disclosure mechanism, simulated here).
NOTE: these are the PLACEHOLDER drawers. The real dock contents are specified
in docs/redesign/dock-spec.md (six drawers: Learning-Behaviour / Inputs /
Outputs-Routing / Powerful Synth Engine / Particle-Visual / Help). */
const DRW_DS = window.ManifoldDesignSystem_490915;
function Chip({ children, tone }) {
return (
<span style={{
fontSize: 10, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.06em',
color: tone || 'var(--fg-mute)', background: 'var(--bg-2)', border: '1px solid var(--line)',
borderRadius: 'var(--r-pill)', padding: '2px 8px',
}}>{children}</span>
);
}
function SectionLabel({ children }) {
return <div style={{ fontSize: 10, color: 'var(--fg-dim)', textTransform: 'uppercase', letterSpacing: '0.1em', marginTop: 'var(--sp-2)' }}>{children}</div>;
}
const PRESETS = ['Sculpt', 'Wander', 'Lock-in', 'Chaos', 'Glide', 'Pin & probe'];
function FeelDrawer(ctx, depth) {
const { ControlAxis, PillToggle } = DRW_DS;
const axes = [
{ key: 'boldness', label: 'Boldness', endpoints: ['Caution', 'Bold'], accent: 'var(--accent)' },
{ key: 'memory', label: 'Memory', endpoints: ['Amnesia', 'Elephant'], accent: 'var(--accent-2)' },
{ key: 'precision', label: 'Precision', endpoints: ['Raw', 'Precise'], accent: 'var(--accent)' },
];
return (
<React.Fragment>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
<Chip tone="var(--accent)">3 axes</Chip>
<Chip>preset · {ctx.preset}</Chip>
{ctx.offsetActive && <Chip tone="var(--accent-2)">offset active</Chip>}
</div>
<SectionLabel>Compound axes</SectionLabel>
{axes.map((a) => (
<ControlAxis key={a.key} label={a.label} endpoints={a.endpoints} accent={a.accent}
value={ctx.axes[a.key]} onChange={(v) => ctx.setAxis(a.key, v)}
preset={a.key === 'boldness' ? (ctx.axes.boldness > 0.66 ? 'explore' : 'steady') : undefined} />
))}
<SectionLabel>Control presets</SectionLabel>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
{PRESETS.map((p) => (
<button key={p} type="button" onClick={() => ctx.setPreset(p)}
style={{
padding: '5px 10px', fontSize: 'var(--fs-xs)', fontFamily: 'var(--font-mono)', cursor: 'pointer',
borderRadius: 'var(--r-pill)', border: `1px solid ${ctx.preset === p ? 'var(--accent)' : 'var(--line)'}`,
background: ctx.preset === p ? 'rgba(255,106,0,0.15)' : 'var(--bg-2)',
color: ctx.preset === p ? 'var(--accent)' : 'var(--fg-mute)',
}}>{p}</button>
))}
</div>
{depth !== 'peek' && (
<React.Fragment>
<SectionLabel>Trim-pot offsets</SectionLabel>
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
Axis tweaks layer as offsets over the active preset. Double-tap an axis to re-link. An <span style={{ color: 'var(--accent-2)' }}>offset-active</span> dot marks divergence.
</p>
</React.Fragment>
)}
</React.Fragment>
);
}
function ShapeDrawer(ctx, depth) {
const { Button, Sparkline, Badge } = DRW_DS;
return (
<React.Fragment>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
<Chip tone="var(--accent)">examples {ctx.datasetCount}</Chip>
<Chip>loss {ctx.loss.length ? ctx.loss[ctx.loss.length - 1].toExponential(1) : '—'}</Chip>
{ctx.busy && <Chip tone="var(--accent-2)">training</Chip>}
</div>
<SectionLabel>Reinforcement (default)</SectionLabel>
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
Use the + / verdicts on the manifold. Every verdict auto-snapshots first undo is consequence-free.
</p>
<SectionLabel>Examples (IML)</SectionLabel>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
<Button size="sm" variant={ctx.addingExample ? 'primary' : 'secondary'} onClick={ctx.onAddExample}>
{ctx.addingExample ? '② store target' : '① add example'}
</Button>
<Button size="sm" onClick={ctx.onTrain} disabled={ctx.busy}>Train</Button>
<Button size="sm" variant="ghost" onClick={ctx.onClear}>clear</Button>
</div>
<SectionLabel>Loss history</SectionLabel>
<Sparkline data={ctx.loss.length ? ctx.loss : [0.5]} log width={depth === 'peek' ? 284 : 480} height={56} />
{depth !== 'peek' && (
<React.Fragment>
<SectionLabel>Snapshot DAG · {ctx.snapshots.length}</SectionLabel>
<ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: 4, maxHeight: 160, overflow: 'auto' }}>
{ctx.snapshots.slice().reverse().map((s, idx) => (
<li key={s.id}>
<button type="button" onClick={() => ctx.onJump(s.id)} style={{
width: '100%', textAlign: 'left', display: 'flex', justifyContent: 'space-between',
background: idx === 0 ? 'var(--bg-3)' : 'var(--bg-2)', border: '1px solid var(--line)',
borderRadius: 'var(--r-1)', padding: '4px 8px', cursor: 'pointer', fontFamily: 'var(--font-mono)', fontSize: 'var(--fs-xs)',
}}>
<span style={{ color: idx === 0 ? 'var(--accent)' : 'var(--fg)' }}>{s.tag}</span>
<span style={{ color: 'var(--fg-dim)' }}>noise {s.noise.toFixed(2)}</span>
</button>
</li>
))}
</ul>
</React.Fragment>
)}
</React.Fragment>
);
}
const STATUS_CYCLE = { off: 'fixed', fixed: 'live', live: 'off' };
const STATUS_COLOR = { off: 'var(--fg-dim)', fixed: 'var(--accent-2)', live: 'var(--accent)' };
function RouteDrawer(ctx, depth) {
const { PillToggle, Badge } = DRW_DS;
const counts = ctx.params.reduce((a, p) => { a[p.status] = (a[p.status] || 0) + 1; return a; }, {});
const rows = depth === 'peek' ? ctx.params.slice(0, 6) : ctx.params;
return (
<React.Fragment>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
<Chip>sources {ctx.mode.inN}/48</Chip>
<Chip>targets {ctx.params.length}/{ctx.params.length}</Chip>
<Chip tone="var(--accent)">live {counts.live || 0}</Chip>
<Chip tone="var(--accent-2)">fixed {counts.fixed || 0}</Chip>
</div>
<SectionLabel>Input source</SectionLabel>
<Badge tone="info">{ctx.mode.input === 'joystick' ? 'joystick' : ctx.mode.input === 'audio_in' ? 'mic (1-input)' : 'xy pad'}</Badge>
<SectionLabel>Output backend</SectionLabel>
<PillToggle value={ctx.outputBackend} onChange={ctx.setOutputBackend}
options={[{ value: 'audio', label: 'Audio' }, { value: 'midi', label: 'MIDI' }, { value: 'osc', label: 'OSC' }, { value: 'cv', label: 'CV' }]} />
<SectionLabel>Control points · off / fixed / live</SectionLabel>
<div style={{ display: 'flex', flexDirection: 'column', gap: 3, maxHeight: depth === 'peek' ? 150 : 320, overflow: 'auto' }}>
{rows.map((p, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 'var(--sp-2)', background: 'var(--bg-2)', border: '1px solid var(--line)', borderRadius: 'var(--r-1)', padding: '4px 8px' }}>
<span style={{ flex: 1, fontSize: 'var(--fs-xs)', color: 'var(--fg)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.name}</span>
<span style={{ fontSize: 10, color: 'var(--fg-dim)' }}>{p.group}</span>
<button type="button" onClick={() => ctx.cycleStatus(ctx.params.indexOf(p))}
style={{ width: 52, fontSize: 10, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.06em',
border: `1px solid ${STATUS_COLOR[p.status]}`, color: STATUS_COLOR[p.status], background: 'transparent',
borderRadius: 'var(--r-1)', padding: '2px 4px', cursor: 'pointer' }}>{p.status}</button>
</div>
))}
</div>
{depth === 'full' && (
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: '8px 0 0', lineHeight: 1.6 }}>
Full tier hosts the Emitters × Targets matrix + per-cell editor + staged-unlock opt-in (built for the modular mega-mode).
</p>
)}
</React.Fragment>
);
}
function GradientBars({ norms, status }) {
const COLOR = { vanishing: 'var(--info)', exploding: 'var(--danger)', converged: 'var(--fg-mute)', healthy: 'var(--good)' };
const max = Math.max(...norms, 0.0001);
return (
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 3, height: 56, background: 'var(--bg-1)', border: '1px solid var(--line)', borderRadius: 'var(--r-2)', padding: 6 }}>
{norms.map((n, i) => (
<div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2, height: '100%', justifyContent: 'flex-end' }}>
<div style={{ width: '100%', height: `${(n / max) * 100}%`, background: COLOR[status[i]] || 'var(--good)', borderRadius: 1 }} />
<span style={{ fontSize: 8, color: 'var(--fg-dim)' }}>L{i}</span>
</div>
))}
</div>
);
}
function HealthDrawer(ctx, depth) {
const { Sparkline, Switch, Slider, Badge } = DRW_DS;
const healthTone = ctx.health > 0.66 ? 'good' : ctx.health > 0.33 ? 'warn' : 'bad';
return (
<React.Fragment>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
<Badge tone={healthTone} dot>{healthTone === 'good' ? 'healthy' : healthTone === 'warn' ? 'drifting' : 'unstable'}</Badge>
<Chip>rev {ctx.weightsRevision}</Chip>
</div>
<SectionLabel>Loss history</SectionLabel>
<Sparkline data={ctx.loss.length ? ctx.loss : [0.5]} log width={depth === 'peek' ? 284 : 480} height={56} />
<SectionLabel>Gradient flow · per layer</SectionLabel>
<GradientBars norms={ctx.gradient} status={ctx.gradientStatus} />
{depth !== 'peek' && (
<React.Fragment>
<SectionLabel>Lab controls</SectionLabel>
<Switch checked={ctx.spread} onChange={ctx.setSpread} label="spread (Xavier regime)" />
<Slider label="tame · output limiter" value={ctx.tame} min={0} max={1} step={0.01} onChange={ctx.setTame} />
<Slider label="noise cap" value={ctx.noiseCap} min={0} max={0.5} step={0.01} onChange={ctx.setNoiseCap} />
</React.Fragment>
)}
</React.Fragment>
);
}
const KEYS = [['15', 'open drawers'], ['\\', 'full depth'], ['space / ↑', 'commit +'], ['↓', 'perturb '], ['z', 'undo'], ['double-click dot', 'follow mode']];
function HelpDrawer(ctx, depth) {
return (
<React.Fragment>
<SectionLabel>Keyboard</SectionLabel>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
{KEYS.map(([k, v]) => (
<div key={k} style={{ display: 'flex', justifyContent: 'space-between', fontSize: 'var(--fs-xs)' }}>
<kbd style={{ background: 'var(--bg-2)', border: '1px solid var(--line)', borderRadius: 'var(--r-1)', padding: '1px 6px', color: 'var(--accent)' }}>{k}</kbd>
<span style={{ color: 'var(--fg-mute)' }}>{v}</span>
</div>
))}
</div>
<SectionLabel>The loop</SectionLabel>
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.7 }}>
Drag the manifold to explore. Hear something good + to settle. Wrong to perturb. Went too far undo. The screen reveals exactly as much machinery as you reach for.
</p>
</React.Fragment>
);
}
window.DRAWERS = {
shape: { icon: '◇', label: 'Shape', render: ShapeDrawer },
feel: { icon: '◎', label: 'Feel', render: FeelDrawer },
route: { icon: '⇄', label: 'Route', render: RouteDrawer },
health: { icon: '♥', label: 'Health', render: HealthDrawer },
help: { icon: '?', label: 'Help', render: HelpDrawer },
};