fix
This commit is contained in:
+59
-19
@@ -1,26 +1,70 @@
|
||||
// Fleet manifest with launch controls.
|
||||
|
||||
import { api } from './api.js';
|
||||
import { h, toast, confirmDialog } from './dom.js';
|
||||
import { h, toast, customDialog, pad } 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.',
|
||||
let slots;
|
||||
try { slots = await api.slots(); } catch (e) { toast(`COULD NOT LOAD YOUR CODE SLOTS: ${e.message}`, 'err'); return; }
|
||||
const slotLabel = (s) => `${pad(s.slot, 2)} ${s.builtin ? `${s.name} (EXAMPLE)` : s.empty ? '(EMPTY)' : s.name}`;
|
||||
let remembered = 0;
|
||||
try { remembered = Number(localStorage.getItem('halcyon.launchSlot')); } catch { /* ignore */ }
|
||||
const usable = slots.filter((s) => !s.empty);
|
||||
const first = usable.find((s) => s.slot === remembered) ?? usable.find((s) => !s.builtin) ?? usable[0];
|
||||
|
||||
const sel = h('select', { id: 'launch-slot' },
|
||||
slots.map((s) => h('option', { value: s.slot, disabled: s.empty, selected: s.slot === first.slot }, slotLabel(s))));
|
||||
const status = h('p', { class: 'launch-status', role: 'status', 'aria-live': 'polite' }, 'CHECKING...');
|
||||
const dlg = customDialog({
|
||||
title: `LAUNCH SHIP #${ship.id}`,
|
||||
content: [
|
||||
h('p', null, 'CHOOSE THE CODE SLOT THIS SHIP WILL FLY. IT IS ASSEMBLED NOW AND SEALED INTO THE SHIP.'),
|
||||
h('label', { for: 'launch-slot' }, 'CODE SLOT'),
|
||||
sel,
|
||||
status,
|
||||
h('p', { class: 'warn' }, 'AT LAUNCH THE PROGRAM IS FIXED. IT CANNOT BE CHANGED, RECALLED OR RESTARTED.'),
|
||||
],
|
||||
confirmText: 'LAUNCH',
|
||||
danger: true,
|
||||
});
|
||||
if (!ok) return;
|
||||
|
||||
// Assemble the chosen slot as soon as it is chosen, so problems show before launch.
|
||||
let seq = 0;
|
||||
const check = async () => {
|
||||
const mine = ++seq;
|
||||
const n = Number(sel.value);
|
||||
dlg.confirm.disabled = true;
|
||||
status.className = 'launch-status';
|
||||
status.textContent = 'ASSEMBLING...';
|
||||
let text;
|
||||
let good = false;
|
||||
try {
|
||||
const src = await api.slot(n);
|
||||
const r = await api.assemble(src.source);
|
||||
if (r.size === 0) text = 'THIS SLOT HAS NO CODE.';
|
||||
else if (!r.fits) text = `TOO LARGE: ${r.size} BYTES, THE LIMIT IS ${r.limit}.`;
|
||||
else { good = true; text = `${r.size} BYTES / ${r.instructions} INSTRUCTIONS: READY TO LAUNCH.`; }
|
||||
} catch (e) {
|
||||
text = `DOES NOT ASSEMBLE: ${e.message}`;
|
||||
}
|
||||
if (mine !== seq) return; // a newer choice superseded this one
|
||||
status.className = `launch-status ${good ? 'ok' : 'bad'}`;
|
||||
status.textContent = text;
|
||||
dlg.confirm.disabled = !good;
|
||||
};
|
||||
sel.addEventListener('change', check);
|
||||
check();
|
||||
|
||||
if (!(await dlg.result)) return;
|
||||
const n = Number(sel.value);
|
||||
try {
|
||||
await api.launch(ship.id);
|
||||
toast(`SHIP #${ship.id} CLEARED FOR LAUNCH. IT FLIES ON THE NEXT DAILY RUN.`);
|
||||
const r = await api.launch(ship.id, n);
|
||||
try { localStorage.setItem('halcyon.launchSlot', String(n)); } catch { /* ignore */ }
|
||||
toast(`SHIP #${ship.id} CLEARED FOR LAUNCH WITH SLOT ${n} (${r?.program_bytes ?? '?'} BYTES). IT FLIES ON THE NEXT DAILY RUN.`);
|
||||
await ctx.refresh();
|
||||
} catch (e) {
|
||||
toast(`LAUNCH FAILED: ${e.message}`, 'err');
|
||||
@@ -34,11 +78,7 @@ export function fleetView(ctx) {
|
||||
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',
|
||||
}));
|
||||
out.push(btn('LAUNCH', () => launch(s), { cls: 'primary', title: 'Choose a code slot and 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')));
|
||||
@@ -72,13 +112,13 @@ export function fleetView(ctx) {
|
||||
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('1', 'WRITE', 'a flight program in the CODE editor and save it in one of your 16 code slots. Your ship cannot be flown by hand.'),
|
||||
step('2', 'TEST', 'it: assemble it in the editor, and improve it in as many slots as you like.'),
|
||||
step('3', 'LAUNCH', 'the ship and choose which slot it flies. 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')),
|
||||
h('p', { class: 'hint pad' }, 'The belt runs once a day. Ore delivered to the station earns credits.'),
|
||||
h('p', { class: 'pad' }, h('a', { class: 'btn small', href: '/manual.pdf', target: '_blank', rel: 'noopener' }, 'OPEN THE HC-33 MANUAL (PDF)')),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user