feat: custom node display names (aliases)
Right-click any node → Rename to set a custom label. The alias is shown
in the node header and Properties dialog instead of the raw PipeWire name.
Leave the input blank (or press Reset) to revert to the PW name.
Aliases are stored in aliases: Record<string,string> inside the patchbay
state (keyed by PW node name, which is stable for hardware devices) and
persisted automatically with the rest of the patchbay config. Old save
files without an aliases key are handled gracefully via the ?? {} fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
setAutoPin, setAutoDisconnect,
|
||||
saveProfile, loadProfile, deleteProfile,
|
||||
setNodeVolume, setNodeMute,
|
||||
setAlias,
|
||||
createNullSink, createLoopback, loadModule,
|
||||
getQuantum, setQuantum,
|
||||
} from '../lib/stores';
|
||||
@@ -29,6 +30,8 @@
|
||||
let contextMenu = $state<{ x: number; y: number; linkId: number; outputPortId: number; inputPortId: number; pinned: boolean } | null>(null);
|
||||
let nodeContextMenu = $state<{ x: number; y: number; nodeId: number; nodeName: string } | null>(null);
|
||||
let showPropsDialog = $state<number | null>(null); // node ID or null
|
||||
let renameDialog = $state<{ pwName: string } | null>(null);
|
||||
let renameInput = $state('');
|
||||
|
||||
// Filters
|
||||
let showAudio = $state(true);
|
||||
@@ -128,6 +131,11 @@
|
||||
return nodeName;
|
||||
}
|
||||
|
||||
// Return custom alias, otherwise nick, otherwise PW name
|
||||
function displayName(nd: { name: string; nick: string }): string {
|
||||
return $patchbay.aliases?.[nd.name] || nd.nick || nd.name;
|
||||
}
|
||||
|
||||
// Build computed layout
|
||||
let graphNodes = $derived.by(() => {
|
||||
const n = $nodes;
|
||||
@@ -593,7 +601,7 @@
|
||||
<rect x={nd.x} y={nd.y} width={nd.width} height="22" rx="4" fill={headerBg} />
|
||||
<rect x={nd.x} y={nd.y + 16} width={nd.width} height="6" fill={headerBg} />
|
||||
<text x={nd.x + 6} y={nd.y + 15} font-size="10" font-family="monospace" fill="#ddd" font-weight="bold">
|
||||
{nd.nick || nd.name}
|
||||
{displayName(nd)}
|
||||
</text>
|
||||
<text x={nd.x + nd.width - 6} y={nd.y + 15} font-size="9" font-family="monospace" fill="#777" text-anchor="end">
|
||||
[{nd.node_type}]
|
||||
@@ -680,8 +688,13 @@
|
||||
|
||||
{#if nodeContextMenu}
|
||||
<div class="ctx" style="left:{nodeContextMenu.x}px;top:{nodeContextMenu.y}px" role="menu">
|
||||
<div class="ctx-title">{nodeContextMenu.nodeName}</div>
|
||||
<div class="ctx-title">{$patchbay.aliases?.[nodeContextMenu.nodeName] || nodeContextMenu.nodeName}</div>
|
||||
<button onclick={() => { showPropsDialog = nodeContextMenu!.nodeId; nodeContextMenu = null; }}>Properties</button>
|
||||
<button onclick={() => {
|
||||
renameDialog = { pwName: nodeContextMenu!.nodeName };
|
||||
renameInput = $patchbay.aliases?.[nodeContextMenu!.nodeName] ?? '';
|
||||
nodeContextMenu = null;
|
||||
}}>Rename</button>
|
||||
<button onclick={() => {
|
||||
fetch('/api/destroy-node', {
|
||||
method: 'POST',
|
||||
@@ -699,7 +712,7 @@
|
||||
{#if nd}
|
||||
<div class="dialog">
|
||||
<div class="dialog-header">
|
||||
<span>Properties: {nd.nick || nd.name}</span>
|
||||
<span>Properties: {displayName(nd)}</span>
|
||||
<button class="close" onclick={() => { showPropsDialog = null; }}>X</button>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
@@ -730,6 +743,31 @@
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- Rename Node Dialog -->
|
||||
{#if renameDialog}
|
||||
<div class="dialog" style="right:auto;left:50%;top:50%;transform:translate(-50%,-50%);width:280px">
|
||||
<div class="dialog-header">
|
||||
<span>Rename Node</span>
|
||||
<button class="close" onclick={() => { renameDialog = null; }}>X</button>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<p class="hint" style="word-break:break-all">{renameDialog.pwName}</p>
|
||||
<div class="input-row">
|
||||
<input
|
||||
class="dlg-input"
|
||||
bind:value={renameInput}
|
||||
placeholder="Custom display name (leave blank to reset)"
|
||||
onkeydown={(e) => { if (e.key === 'Enter') { setAlias(renameDialog!.pwName, renameInput); renameDialog = null; } }}
|
||||
/>
|
||||
</div>
|
||||
<div class="input-row" style="justify-content:flex-end;gap:6px">
|
||||
<button onclick={() => { setAlias(renameDialog!.pwName, ''); renameDialog = null; }}>Reset</button>
|
||||
<button onclick={() => { setAlias(renameDialog!.pwName, renameInput); renameDialog = null; }}>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Hide Nodes Dialog -->
|
||||
{#if showHideDialog}
|
||||
<div class="dialog">
|
||||
|
||||
@@ -19,6 +19,7 @@ export const patchbay = writable<PatchbayState>({
|
||||
pinned_connections: [],
|
||||
hide_rules: [],
|
||||
merge_rules: [],
|
||||
aliases: {},
|
||||
});
|
||||
|
||||
// Port/node lookups
|
||||
@@ -82,7 +83,7 @@ export async function initGraph() {
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
if (data && data.profiles) {
|
||||
patchbay.set(data as PatchbayState);
|
||||
patchbay.set({ ...data, aliases: data.aliases ?? {} } as PatchbayState);
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
@@ -357,6 +358,20 @@ export function deleteProfile(name: string) {
|
||||
savePatchbayState();
|
||||
}
|
||||
|
||||
// Node aliases (custom display names, keyed by PW node name)
|
||||
export function setAlias(pwName: string, alias: string) {
|
||||
patchbay.update(pb => {
|
||||
const aliases = { ...pb.aliases };
|
||||
if (alias.trim()) {
|
||||
aliases[pwName] = alias.trim();
|
||||
} else {
|
||||
delete aliases[pwName]; // empty string = remove alias
|
||||
}
|
||||
return { ...pb, aliases };
|
||||
});
|
||||
savePatchbayState();
|
||||
}
|
||||
|
||||
// Volume control
|
||||
export async function setNodeVolume(nodeId: number, volume: number) {
|
||||
try {
|
||||
|
||||
@@ -85,4 +85,5 @@ export interface PatchbayState {
|
||||
pinned_connections: number[];
|
||||
hide_rules: string[];
|
||||
merge_rules: string[];
|
||||
aliases: Record<string, string>; // PW node name → custom display name
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user