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-10 21:10:20 +00:00
let spotDom = null ;
function openSpot ( open ) {
2026-11-09 22:22:46 +00:00
ui . spot = open ;
2026-11-10 21:10:20 +00:00
spotDom . panel . hidden = ! open ;
spotDom . toggle . setAttribute ( "aria-expanded" , String ( open ));
2026-11-09 22:22:46 +00:00
window . requestAnimationFrame ( refresh3d );
if ( open ) {
2026-11-10 21:10:20 +00:00
spotDom . text . focus ();
2026-11-09 22:22:46 +00:00
}
}
2026-11-10 21:10:20 +00:00
function toggleGear () {
const open = spotDom . gearBox . hidden ;
spotDom . gearBox . replaceChildren (...( open ? [ radioSettings ( toggleGear )] : []));
spotDom . gearBox . hidden = ! open ;
spotDom . gear . setAttribute ( "aria-expanded" , String ( open ));
2026-11-09 22:22:46 +00:00
window . requestAnimationFrame ( refresh3d );
}
function radioSpot () {
const { form , recipient , text } = composerForm ();
const gearBox = h ( "div" , { class : "radio-gear" , id : "radio-gear" , hidden : true });
2026-11-10 21:10:20 +00:00
const gear = iconBtn ( "bi-gear" , "Radio settings" , "btn small ghost spot-toggle" , toggleGear , "Radio settings: tones, squelch and read aloud" );
gear . setAttribute ( "aria-expanded" , "false" );
gear . setAttribute ( "aria-controls" , "radio-gear" );
const [, label , , hint ] = actionOf ( "clear-mayday" );
const clear = iconBtn ( "bi-life-preserver" , label , "btn small danger" , () => confirmAct ( "clear-mayday" ), hint );
2026-11-09 22:22:46 +00:00
const panel = h ( "section" , { class : "card radio-spot" , id : "radio-spot" , "aria-label" : "Radio" , hidden : ! ui . spot },
2026-11-10 21:10:20 +00:00
h ( "div" , { class : "spot-head" }, h ( "h3" , { class : "group-title" }, "Radio" ), h ( "span" , { class : "spacer" }), gear , clear ), h ( "div" , { class : "spot-body" }, form , radioSide (), gearBox ));
const toggle = document . getElementById ( "radio-open" );
toggle . setAttribute ( "aria-expanded" , String ( ui . spot ));
toggle . addEventListener ( "click" , () => openSpot ( ! ui . spot ));
document . getElementById ( "spot-host" ). replaceChildren ( panel );
spotDom = { panel , toggle , gear , gearBox , text , clear , recipient , names : "" };
2026-11-09 22:22:46 +00:00
}
function openRadio () {
2026-11-10 21:10:20 +00:00
openSpot ( true );
}
function drawSpot () {
spotDom . clear . disabled = ! state . silence ;
const names = agentNames ();
if ( JSON . stringify ( names ) !== spotDom . names ) {
spotDom . names = JSON . stringify ( names );
const chosen = spotDom . recipient . value ;
spotDom . recipient . replaceChildren (... names . map (( name ) => h ( "option" , { value : name , selected : name === chosen }, name )));
}
2026-11-06 22:20:06 +00:00
}
function agentsToolbar () {
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" )))));
2026-11-10 21:10:20 +00:00
const bar = h ( "div" , { class : "toolbar" , role : "toolbar" , "aria-label" : "Controls" }, iconBtn ( "bi-sort-down" , "Priority" , "btn small" , openAgentOrder , "Priority: agent order in the hierarchy" ), night );
return { bar , mode };
2026-11-06 22:20:06 +00:00
}
function drawToolbar () {
2026-11-10 21:10:20 +00:00
drawSpot ();
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 . mode . replaceChildren ( pill ( mode , tone ));
2026-11-06 22:20:06 +00:00
}