mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
306e56dc2b
Pull pure helpers, the contributors/maintainers data list and the tab type/search index out of `Settings.tsx` so the main component only has tab-section render logic + the orchestrating state left: - `utils/audioDeviceLabels.ts` — five ALSA-device label helpers (formatAudioDeviceLabel + duplicate-disambiguation + sort + select-option builder). - `utils/formatBytes.ts` — formatBytes + snapHotCacheMb. - `components/settings/LoudnessLufsButtonGroup.tsx` — small chip group used by the audio tab. - `components/settings/settingsTabs.ts` — `Tab` type + legacy alias map + `resolveTab` + `SearchIndexEntry` + `SETTINGS_INDEX` + the `matchScore` substring-with-fuzzy-fallback scorer. - `config/settingsCredits.ts` — `CONTRIBUTORS` (~270 LOC of static contributor history) + `MAINTAINERS`. Pure code-move. Settings.tsx: 3552 → 3037 LOC (−515). Settings/G journey now 5298 → 3037 LOC (~43% reduction).
119 lines
4.8 KiB
TypeScript
119 lines
4.8 KiB
TypeScript
/** Makes raw ALSA device names more readable on Linux.
|
|
* Values are kept as-is (rodio needs the ALSA name); only the displayed label is cleaned.
|
|
* e.g. "sysdefault:CARD=U192k" → "U192k"
|
|
* "hw:CARD=U192k,DEV=0" → "U192k (hw · PCM 0)"
|
|
* "hdmi:CARD=NVidia,DEV=1" → "NVidia (HDMI · DEV 1)" (same DEV as in ALSA string)
|
|
* "iec958:CARD=PCH,DEV=0" → "PCH (S/PDIF)"
|
|
* Names without ALSA prefix (pipewire, pulse, default…) are returned unchanged. */
|
|
export function formatAudioDeviceLabel(name: string): string {
|
|
const cardMatch = name.match(/CARD=([^,]+)/);
|
|
if (!cardMatch) return name;
|
|
const card = cardMatch[1];
|
|
const devM = name.match(/DEV=(\d+)/);
|
|
const devNum = devM ? parseInt(devM[1], 10) : null;
|
|
const subM = name.match(/SUBDEV=(\d+)/);
|
|
const subNum = subM ? parseInt(subM[1], 10) : null;
|
|
|
|
if (name.startsWith('iec958:')) return `${card} (S/PDIF)`;
|
|
if (name.startsWith('hdmi:')) {
|
|
const d = devNum !== null ? devNum : 0;
|
|
return `${card} (HDMI · DEV ${d})`;
|
|
}
|
|
if (name.startsWith('sysdefault:')) {
|
|
if (devNum !== null && devNum > 0) return `${card} (default · PCM ${devNum})`;
|
|
return card;
|
|
}
|
|
if (name.startsWith('plughw:')) {
|
|
if (devNum !== null) {
|
|
const sub = subNum !== null ? ` · sub ${subNum}` : '';
|
|
return `${card} (plug · PCM ${devNum}${sub})`;
|
|
}
|
|
return card;
|
|
}
|
|
if (name.startsWith('hw:')) {
|
|
if (devNum !== null) {
|
|
const sub = subNum !== null ? ` · sub ${subNum}` : '';
|
|
return `${card} (hw · PCM ${devNum}${sub})`;
|
|
}
|
|
return `${card} (hw)`;
|
|
}
|
|
if (name.startsWith('front:')) return `${card} (Front)`;
|
|
if (name.startsWith('surround')) return `${card} (${name.split(':')[0]})`;
|
|
// Other ALSA iface:card,dev — show plugin + PCM so identical cards differ
|
|
const iface = name.split(':')[0];
|
|
if (iface && !['default', 'pulse', 'pipewire'].includes(iface)) {
|
|
if (devNum !== null) return `${card} (${iface} · PCM ${devNum})`;
|
|
return `${card} (${iface})`;
|
|
}
|
|
return card;
|
|
}
|
|
|
|
/** Readable tail when two devices still share the same label (rare after formatAudioDeviceLabel). */
|
|
export function audioDeviceDuplicateHint(raw: string): string {
|
|
const cardM = raw.match(/CARD=([^,]+)/);
|
|
const devM = raw.match(/DEV=(\d+)/);
|
|
const subM = raw.match(/SUBDEV=(\d+)/);
|
|
const iface = raw.split(':')[0] || '';
|
|
const parts: string[] = [];
|
|
if (iface) parts.push(iface);
|
|
if (cardM) parts.push(cardM[1]);
|
|
if (devM) parts.push(`PCM ${devM[1]}`);
|
|
if (subM) parts.push(`sub ${subM[1]}`);
|
|
if (parts.length > 1) return parts.join(' · ');
|
|
return raw.length > 56 ? `…${raw.slice(-53)}` : raw;
|
|
}
|
|
|
|
/** When several devices share the same display label, append a disambiguator. */
|
|
export function disambiguatedAudioDeviceLabel(raw: string, baseLabel: string, duplicateBase: boolean): string {
|
|
if (!duplicateBase) return baseLabel;
|
|
return `${baseLabel} · ${audioDeviceDuplicateHint(raw)}`;
|
|
}
|
|
|
|
/** cpal order is arbitrary; sort by readable label, current OS default first. */
|
|
export function sortAudioDeviceIds(devices: string[], osDefaultDeviceId: string | null): string[] {
|
|
return [...devices].sort((a, b) => {
|
|
const aDef = osDefaultDeviceId && a === osDefaultDeviceId;
|
|
const bDef = osDefaultDeviceId && b === osDefaultDeviceId;
|
|
if (aDef !== bDef) return aDef ? -1 : 1;
|
|
const la = formatAudioDeviceLabel(a);
|
|
const lb = formatAudioDeviceLabel(b);
|
|
const byLabel = la.localeCompare(lb, undefined, { sensitivity: 'base' });
|
|
if (byLabel !== 0) return byLabel;
|
|
return a.localeCompare(b);
|
|
});
|
|
}
|
|
|
|
export function buildAudioDeviceSelectOptions(
|
|
devices: string[],
|
|
defaultLabel: string,
|
|
osDefaultDeviceId: string | null,
|
|
osDefaultMark: string,
|
|
pinnedDevice: string | null,
|
|
notInListSuffix: string,
|
|
): { value: string; label: string }[] {
|
|
const baseLabels = devices.map(formatAudioDeviceLabel);
|
|
const countByBase = new Map<string, number>();
|
|
for (const b of baseLabels) countByBase.set(b, (countByBase.get(b) ?? 0) + 1);
|
|
const pinned = pinnedDevice?.trim() || null;
|
|
const pinnedNotListed = !!(pinned && !devices.includes(pinned));
|
|
const ghost: { value: string; label: string }[] = pinnedNotListed
|
|
? (() => {
|
|
const base = formatAudioDeviceLabel(pinned);
|
|
let label = `${base} · ${notInListSuffix}`;
|
|
if (osDefaultDeviceId && pinned === osDefaultDeviceId) label = `${label} · ${osDefaultMark}`;
|
|
return [{ value: pinned, label }];
|
|
})()
|
|
: [];
|
|
return [
|
|
{ value: '', label: defaultLabel },
|
|
...ghost,
|
|
...devices.map((d, i) => {
|
|
const base = baseLabels[i];
|
|
const dup = (countByBase.get(base) ?? 0) > 1;
|
|
let label = disambiguatedAudioDeviceLabel(d, base, dup);
|
|
if (osDefaultDeviceId && d === osDefaultDeviceId) label = `${label} · ${osDefaultMark}`;
|
|
return { value: d, label };
|
|
}),
|
|
];
|
|
}
|