85 lines
3.7 KiB
JavaScript
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')),
|
|
),
|
|
);
|
|
}
|