Files
2026-09-19 20:21:47 +02:00

85 lines
3.7 KiB
JavaScript

// Fleet manifest with launch controls.
import { api } from './api.js';
import { h, toast, confirmDialog } from './dom.js';
import { statusOf } from './status.js';
export function fleetView(ctx) {
const { ships, selected } = ctx.state;
async function launch(ship) {
const ok = await confirmDialog({
title: `LAUNCH SHIP #${ship.id}?`,
body: [
'THE SHIP ENTERS THE BELT ON THE NEXT DAILY RUN.',
'ITS PROGRAM IS SEALED AT LAUNCH. IT CANNOT BE CHANGED, RECALLED OR RESTARTED.',
],
confirmText: 'LAUNCH',
danger: true,
});
if (!ok) return;
try {
await api.launch(ship.id);
toast(`SHIP #${ship.id} CLEARED FOR LAUNCH. IT FLIES ON THE NEXT DAILY RUN.`);
await ctx.refresh();
} catch (e) {
toast(`LAUNCH FAILED: ${e.message}`, 'err');
}
}
const btn = (label, onclick, opts = {}) =>
h('button', { class: `btn small ${opts.cls ?? ''}`, type: 'button', onclick, disabled: opts.disabled, title: opts.title }, label);
function actions(s) {
const out = [];
if (s.status === 'inventory') {
out.push(btn('CODE', () => ctx.selectShip(s.id, 'code')));
out.push(btn('LAUNCH', () => launch(s), {
cls: 'primary',
disabled: s.program_bytes === 0,
title: s.program_bytes === 0 ? 'Upload a program first' : 'Launch on the next daily run',
}));
}
if (s.status === 'launching' || s.status === 'active') out.push(btn('UPLINK', () => ctx.selectShip(s.id, 'uplink')));
if (s.downlink_day >= 0) out.push(btn('DOWNLINK', () => ctx.selectShip(s.id, 'downlink')));
return out;
}
const rows = ships.map((s) => {
const st = statusOf(s.status);
return h('tr', { class: s.id === selected ? 'sel' : '' },
h('td', { 'data-label': 'SHIP' }, h('button', { class: 'linklike', type: 'button', onclick: () => ctx.selectShip(s.id) }, `#${s.id}`)),
h('td', { 'data-label': 'STATUS' }, h('span', { class: `badge ${st.cls}`, title: st.hint }, st.label)),
h('td', { 'data-label': 'PROGRAM' }, s.program_bytes ? `${s.program_bytes} BYTES` : h('span', { class: 'dim' }, 'NONE')),
h('td', { 'data-label': 'LAST DOWNLINK' }, s.downlink_day >= 0 ? `DAY ${s.downlink_day}` : h('span', { class: 'dim' }, '--')),
h('td', { class: 'actions', 'data-label': '' }, actions(s)),
);
});
const step = (n, title, text) => h('li', null, h('span', { class: 'n' }, n), h('div', null, h('strong', null, title), ' ', text));
return h('div', { class: 'grid-2 fleet-grid' },
h('section', { class: 'panel' },
h('div', { class: 'panel-title' }, 'FLEET MANIFEST'),
h('div', { class: 'panel-body flush' },
ships.length
? h('table', { class: 'manifest' },
h('thead', null, h('tr', null, ['SHIP', 'STATUS', 'PROGRAM', 'LAST DOWNLINK', ''].map((t) => h('th', { scope: 'col' }, t)))),
h('tbody', null, rows))
: h('p', { class: 'empty' }, 'NO SHIPS ON RECORD.'),
),
),
h('aside', { class: 'panel brief' },
h('div', { class: 'panel-title' }, 'MISSION BRIEF'),
h('ol', { class: 'steps' },
step('1', 'WRITE', 'a flight program in the CODE editor. Your ship cannot be flown by hand.'),
step('2', 'SEAL', 'it into a docked ship. You can replace it until launch.'),
step('3', 'LAUNCH', 'the ship. It enters the belt on the next daily run and the program is fixed for good.'),
step('4', 'TALK', 'to it once a day: 1 KB up, 1 KB down, over the wormhole link.'),
),
h('p', { class: 'hint pad' }, 'The belt is simulated once a day. Ore delivered to the station earns credits.'),
h('p', { class: 'pad' }, h('a', { class: 'btn small', href: '/manual.txt', target: '_blank', rel: 'noopener' }, 'OPEN THE HC-33 MANUAL')),
),
);
}