// Cluster command macros — a named button for a command you would otherwise // retype. // // Twelve slots, fixed. A list you can grow needs add/remove/reorder controls and // a decision about what an empty row means; twelve boxes you fill in need // neither, and nobody has thirteen cluster commands they use daily. A slot with // no command is simply not drawn, so the toolbar is as long as the operator made // it and no longer. // // Stored through writeUiPref like every other portable preference, so the // buttons travel with data/ rather than living in one browser profile. import { writeUiPref } from '@/lib/uiPref'; export type ClusterMacro = { label: string; // what the button says cmd: string; // what is sent to the master server }; export const CLUSTER_MACRO_COUNT = 12; export const clusterMacrosKey = 'opslog.clusterMacros'; export const emptyClusterMacros = (): ClusterMacro[] => Array.from({ length: CLUSTER_MACRO_COUNT }, () => ({ label: '', cmd: '' })); // loadClusterMacros always returns exactly CLUSTER_MACRO_COUNT entries, whatever // was stored: a saved list from a build with fewer slots must not leave the // editor rendering undefined rows. export function loadClusterMacros(): ClusterMacro[] { const out = emptyClusterMacros(); try { const raw = localStorage.getItem(clusterMacrosKey); if (!raw) return out; const v = JSON.parse(raw); if (!Array.isArray(v)) return out; for (let i = 0; i < CLUSTER_MACRO_COUNT && i < v.length; i++) { out[i] = { label: String(v[i]?.label ?? '').slice(0, 24), cmd: String(v[i]?.cmd ?? '').slice(0, 120), }; } } catch { /* a corrupt preference is not worth failing the panel over */ } return out; } export function saveClusterMacros(macros: ClusterMacro[]): void { writeUiPref(clusterMacrosKey, JSON.stringify(macros)); } // visibleClusterMacros drops the slots that would send nothing. The COMMAND is // what decides: a slot with a label and no command is a button that lies, and a // command with no label still has something to show — its own text. export function visibleClusterMacros(macros: ClusterMacro[]): ClusterMacro[] { return macros .filter((m) => m.cmd.trim() !== '') .map((m) => ({ label: m.label.trim() || m.cmd.trim(), cmd: m.cmd.trim() })); }