feat: pipedal SVG icons in block tiles + nav bar
- Replace emoji getBlockIcon() with <BlockIcon> component rendering SVG icons - 38 fx_*.svg icons from pipedal mapped to block types (distortion, delay, reverb, mod, etc.) - Split/merge tiles now use fx_split_a.svg / fx_mixer.svg - Status bar: ic_bank.svg, ic_presets.svg, fx_analyzer.svg, ic_drawer_2.svg - Extracted reusable CSS design system to index.css (badge, snap-grid, slide-panel, etc.) - SnapshotPanel and component reference extracted to docs/ - 270KB JS build, SVG icons served from /ui/img/
This commit is contained in:
+67
-27
@@ -40,6 +40,70 @@ const TYPE_COLORS = {
|
||||
split: "#B080C0", merge: "#80B0C0",
|
||||
};
|
||||
|
||||
// ── Type-to-SVG-icon mapping (from pipedal icon set) ──
|
||||
const TYPE_ICON_MAP = {
|
||||
od: "fx_distortion", overdrive: "fx_distortion", drive: "fx_distortion",
|
||||
dist: "fx_distortion", distortion: "fx_distortion", gain: "fx_distortion",
|
||||
fuzz: "fx_distortion",
|
||||
delay: "fx_delay", echo: "fx_delay",
|
||||
reverb: "fx_reverb",
|
||||
chorus: "fx_chorus", flange: "fx_flanger", flanger: "fx_flanger",
|
||||
phaser: "fx_phaser", tremolo: "fx_modulator", vibrato: "fx_modulator",
|
||||
rotary: "fx_modulator", modulator: "fx_modulator",
|
||||
pitch: "fx_pitch", harmonizer: "fx_pitch", shifter: "fx_pitch",
|
||||
comp: "fx_compressor", compressor: "fx_compressor",
|
||||
limiter: "fx_limiter",
|
||||
gate: "fx_gate", noise_gate: "fx_gate",
|
||||
filter: "fx_filter", wah: "fx_filter",
|
||||
eq: "fx_eq", graphic_eq: "fx_eq",
|
||||
parametric_eq: "fx_parametric_eq", multiband_eq: "fx_multiband_eq",
|
||||
ir: "fx_spatial", cab: "fx_spatial", cabinet: "fx_spatial",
|
||||
nam: "fx_simulator", capture: "fx_simulator", amp: "fx_amplifier",
|
||||
amplifier: "fx_amplifier",
|
||||
volume: "fx_constant", level: "fx_constant", boost: "fx_constant",
|
||||
split: "fx_split_a", merge: "fx_mixer",
|
||||
tuner: "fx_analyzer",
|
||||
mixer: "fx_mixer", utility: "fx_utility",
|
||||
plugin: "fx_plugin",
|
||||
instrument: "fx_instrument",
|
||||
reamp: "fx_converter",
|
||||
looper: "fx_oscillator",
|
||||
};
|
||||
|
||||
const IMG_PATH = (import.meta.env.BASE_URL || '/ui/') + 'img/';
|
||||
|
||||
// ── SVG Block Icon component ─────────────────────────
|
||||
function BlockIcon({ type, size = 14, style = {} }) {
|
||||
const key = (type || '').toLowerCase().trim();
|
||||
let icon = 'fx_plugin';
|
||||
let found = false;
|
||||
for (const [k, v] of Object.entries(TYPE_ICON_MAP)) {
|
||||
if (key === k || key.startsWith(k) || key.includes(k)) {
|
||||
icon = v;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found && key) {
|
||||
// Try direct match by replacing spaces/special chars
|
||||
const slug = key.replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '');
|
||||
if (TYPE_ICON_MAP[slug]) icon = TYPE_ICON_MAP[slug];
|
||||
}
|
||||
return (
|
||||
<img
|
||||
src={`${IMG_PATH}${icon}.svg`}
|
||||
alt={type || 'fx'}
|
||||
style={{
|
||||
width: size, height: size, flexShrink: 0,
|
||||
filter: 'brightness(1.2) contrast(1.1)',
|
||||
objectFit: 'contain',
|
||||
...style,
|
||||
}}
|
||||
onError={(e) => { e.target.style.display = 'none'; }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function getBlockColor(type) {
|
||||
const key = (type || "").toLowerCase().trim();
|
||||
for (const [k, v] of Object.entries(TYPE_COLORS)) {
|
||||
@@ -48,30 +112,6 @@ function getBlockColor(type) {
|
||||
return T.amber;
|
||||
}
|
||||
|
||||
function getBlockIcon(type) {
|
||||
const key = (type || "").toLowerCase();
|
||||
if (key === "split") return "⇄";
|
||||
if (key === "merge") return "⨁";
|
||||
if (key.includes("dist") || key.includes("gain")) return "💥";
|
||||
if (key.includes("fuzz")) return "⚡";
|
||||
if (key.includes("delay") || key.includes("echo")) return "⏳";
|
||||
if (key.includes("reverb")) return "🌊";
|
||||
if (key.includes("pitch") || key.includes("harm") || key.includes("shift")) return "🎵";
|
||||
if (key.includes("comp") || key.includes("limit")) return "📊";
|
||||
if (key.includes("gate") || key.includes("noise")) return "🔇";
|
||||
if (key.includes("filter") || key.includes("wah")) return "📈";
|
||||
if (key.includes("eq")) return "⚖️";
|
||||
if (odMatch(key)) return "🔥";
|
||||
if (modMatch(key)) return "🌀";
|
||||
if (key.includes("ir") || key.includes("cab")) return "🔊";
|
||||
if (key.includes("nam") || key.includes("capture")) return "🎛";
|
||||
if (key.includes("volume") || key.includes("level") || key.includes("boost")) return "🔈";
|
||||
if (key.includes("tuner")) return "🎹";
|
||||
return "▣";
|
||||
function odMatch(k) { return k.includes("od")||k.includes("overdrive")||k.includes("drive")||k.includes("boost")&&!k.includes("volume"); }
|
||||
function modMatch(k) { return k.includes("mod")||k.includes("chorus")||k.includes("flange")||k.includes("phaser")||k.includes("trem")||k.includes("vibr")||k.includes("rotary"); }
|
||||
}
|
||||
|
||||
// ── Chain Arrow SVG ──────────────────────────────
|
||||
function ChainArrow({ color = T.border, narrow = false }) {
|
||||
const w = narrow ? 14 : 18;
|
||||
@@ -166,7 +206,7 @@ function SplitBlockTile({ block, selected, onSelect, onToggle }) {
|
||||
whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
|
||||
lineHeight: 1.3, display: "flex", alignItems: "center", gap: 6,
|
||||
}}>
|
||||
<span style={{ fontSize: 14 }}>⇄</span>
|
||||
<BlockIcon type="split" size={16} />
|
||||
<span>SPLIT {st.label}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 9, color: T.textSec, marginTop: 2 }}>
|
||||
@@ -234,7 +274,7 @@ function MergeBlockTile({ block, selected, onSelect, onToggle }) {
|
||||
whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis",
|
||||
lineHeight: 1.3, display: "flex", alignItems: "center", gap: 6,
|
||||
}}>
|
||||
<span style={{ fontSize: 14 }}>⨁</span>
|
||||
<BlockIcon type="merge" size={16} />
|
||||
<span>MERGE</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 9, color: T.textSec, marginTop: 2 }}>
|
||||
@@ -332,7 +372,7 @@ function BlockTile({ block, selected, onSelect, onToggle, pathLabel }) {
|
||||
{block.name || block.type || "FX"}
|
||||
</div>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 5, marginTop: 3 }}>
|
||||
<span style={{ fontSize: 12, lineHeight: 1 }}>{getBlockIcon(block.type)}</span>
|
||||
<BlockIcon type={block.type} size={14} />
|
||||
<span style={{
|
||||
fontSize: 9, fontWeight: 700, color, letterSpacing: ".08em",
|
||||
textTransform: "uppercase", background: `${color}18`,
|
||||
|
||||
Reference in New Issue
Block a user