2026-11-06 22:20:06 +00:00
async function act ( action , label , payload ) {
ui . busy = true ;
const result = await sendJson ( `/api/board/ ${ action } ` , payload );
ui . busy = false ;
if ( result . data && result . data . runs ) {
2026-11-08 17:26:51 +00:00
toast ( ` ${ label } : ${ result . data . ok ? "done" : "failed" } ` , runLines ( result . data ), result . data . ok );
2026-11-06 22:20:06 +00:00
} else {
2026-11-08 17:26:51 +00:00
toast ( ` ${ label } : failed` , [ ` ${ result . status } ${ result . text } ` . trim ()], false );
2026-11-06 22:20:06 +00:00
}
await load ();
return result . ok && result . data && result . data . ok ;
}
2026-11-09 22:22:46 +00:00
const actionOf = ( action ) => ACTIONS . find (([ id ]) => id === action );
2026-11-07 19:59:47 +00:00
async function confirmAct ( action ) {
2026-11-09 22:22:46 +00:00
const [, label , question ] = actionOf ( action );
2026-11-07 19:59:47 +00:00
if ( ! window . confirm ( question )) {
return ;
}
const signed = await signedProof ( "/api/board/sign" , { action }, label );
if ( signed ) {
await act ( action , label , { proof : signed . proof });
2026-11-06 22:20:06 +00:00
}
}
function choice ( name , options , picked ) {
2026-11-08 17:26:51 +00:00
return h ( "div" , { class : "classes" , role : "radiogroup" , "aria-label" : "Class" }, options . map (([ id , label ]) => h ( "label" , { class : "check" },
2026-11-06 22:20:06 +00:00
h ( "input" , { type : "radio" , name , value : id , checked : id === picked }), label )));
}
const picked = ( group ) => group . querySelector ( "input:checked" ). value ;
2026-11-09 22:22:46 +00:00
function actionButton ( action , cls , name ) {
const [, label , , hint ] = actionOf ( action );
return h ( "button" , { type : "button" , class : cls , ... named ( label , hint ), on : { click : () => confirmAct ( action )}}, name ? icon ( name ) : null , label );
2026-11-06 22:20:06 +00:00
}
2026-11-09 22:22:46 +00:00
function openSpot ( spot , open ) {
ui . spot = open ;
spot . panel . hidden = ! open ;
spot . toggle . setAttribute ( "aria-expanded" , String ( open ));
window . requestAnimationFrame ( refresh3d );
if ( open ) {
spot . text . focus ();
}
}
function toggleGear ( spot ) {
const open = spot . gearBox . hidden ;
spot . gearBox . replaceChildren (...( open ? [ radioSettings (() => toggleGear ( spot ))] : []));
spot . gearBox . hidden = ! open ;
spot . gear . setAttribute ( "aria-expanded" , String ( open ));
window . requestAnimationFrame ( refresh3d );
}
function radioSpot () {
const { form , recipient , text } = composerForm ();
const gearBox = h ( "div" , { class : "radio-gear" , id : "radio-gear" , hidden : true });
const gear = h ( "button" , { type : "button" , class : "btn small ghost icon-only spot-toggle" , "aria-expanded" : "false" , "aria-controls" : "radio-gear" , ... named ( "Radio settings" , "Radio settings: tones, squelch and read aloud" )}, icon ( "bi-gear" ));
const clear = actionButton ( "clear-mayday" , "btn small danger" , "bi-life-preserver" );
const panel = h ( "section" , { class : "card radio-spot" , id : "radio-spot" , "aria-label" : "Radio" , hidden : ! ui . spot },
h ( "div" , { class : "spot-head" }, h ( "h3" , { class : "group-title" }, "Radio" ), h ( "span" , { class : "spacer" }), gear , clear ), h ( "div" , { class : "spot-body" }, form , gearBox ));
const toggle = h ( "button" , { type : "button" , class : "btn small spot-toggle" , "aria-expanded" : String ( ui . spot ), "aria-controls" : "radio-spot" , ... named ( "Radio" , "Radio: post, call or a message to all, with Record and the radio settings" )}, icon ( "bi-broadcast-pin" ), caret ());
const spot = { panel , toggle , gear , gearBox , text };
toggle . addEventListener ( "click" , () => openSpot ( spot , ! ui . spot ));
gear . addEventListener ( "click" , () => toggleGear ( spot ));
return { toggle , panel , clear , recipient };
}
function openRadio () {
ui . spot = true ;
2026-11-06 22:20:06 +00:00
go ( "agents" , null );
window . setTimeout (() => {
2026-11-09 22:22:46 +00:00
const field = document . querySelector ( "#radio-spot textarea" );
if ( field ) {
field . focus ();
2026-11-06 22:20:06 +00:00
}
}, 50 );
}
function agentsToolbar () {
2026-11-09 22:22:46 +00:00
const spot = radioSpot ();
2026-11-06 22:20:06 +00:00
const mode = h ( "span" , {});
2026-11-08 17:26:51 +00:00
const night = h ( "details" , { class : "menu" }, h ( "summary" , named ( "Day/Night" ), icon ( "bi-moon-stars" ), caret ()), h ( "div" , { class : "menu-box" }, mode ,
2026-11-09 22:22:46 +00:00
h ( "div" , { class : "actions" }, ACTIONS . slice ( 1 ). map (([ id ]) => actionButton ( id , "btn small" )))));
const bar = h ( "div" , { class : "toolbar" , role : "toolbar" , "aria-label" : "Controls" }, spot . toggle , iconBtn ( "bi-sort-down" , "Priority" , "btn small" , openAgentOrder , "Priority: agent order in the hierarchy" ), night );
return { bar , spot : spot . panel , clear : spot . clear , recipient : spot . recipient , mode , names : "" };
2026-11-06 22:20:06 +00:00
}
function drawToolbar () {
2026-11-09 22:22:46 +00:00
const toolbar = graphDom && graphDom . toolbar ;
if ( ! toolbar ) {
2026-11-06 22:20:06 +00:00
return ;
}
const [ mode , tone ] = NIGHT [ state . night ] || [ state . night , "neutral" ];
2026-11-09 22:22:46 +00:00
toolbar . clear . disabled = ! state . silence ;
toolbar . mode . replaceChildren ( pill ( mode , tone ));
const names = agentNames ();
if ( JSON . stringify ( names ) !== toolbar . names ) {
toolbar . names = JSON . stringify ( names );
const chosen = toolbar . recipient . value ;
toolbar . recipient . replaceChildren (... names . map (( name ) => h ( "option" , { value : name , selected : name === chosen }, name )));
}
2026-11-06 22:20:06 +00:00
}