94 lines
4.0 KiB
JavaScript
94 lines
4.0 KiB
JavaScript
// Downlink viewer: hex dump of the ship's transmit buffer, with download.
|
|
|
|
import { api } from './api.js';
|
|
import { h, clear, toast, download } from './dom.js';
|
|
import { hexView } from './hexview.js';
|
|
|
|
export function downlinkView(ctx) {
|
|
const ship = ctx.ship;
|
|
const body = h('div', { class: 'panel-body' }, h('p', { class: 'dim' }, ship ? 'ACQUIRING SIGNAL...' : 'YOU HAVE NO SHIP.'));
|
|
const meta = h('span', { class: 'dim' });
|
|
const dl = h('button', { class: 'btn', type: 'button', disabled: true }, 'DOWNLOAD .BIN');
|
|
const refresh = h('button', { class: 'btn', type: 'button', disabled: !ship }, 'REFRESH');
|
|
|
|
// Earlier downlinks: pick a day, or step with the arrows. `days` is oldest first.
|
|
const older = h('button', { class: 'btn', type: 'button', disabled: true, 'aria-label': 'Previous downlink' }, '<');
|
|
const newer = h('button', { class: 'btn', type: 'button', disabled: true, 'aria-label': 'Next downlink' }, '>');
|
|
const pick = h('select', { class: 'daypick', 'aria-label': 'Downlink day', disabled: true });
|
|
let days = [];
|
|
let shown = null; // the day on screen; null = the latest
|
|
|
|
function syncPicker() {
|
|
clear(pick).append(...days.slice().reverse().map((d, i) =>
|
|
h('option', { value: String(d) }, `DAY ${d}${i === 0 ? ' (LATEST)' : ''}`)));
|
|
const at = shown == null ? days.length - 1 : days.indexOf(shown);
|
|
if (at >= 0) pick.value = String(days[at]);
|
|
pick.disabled = days.length < 2;
|
|
older.disabled = at <= 0;
|
|
newer.disabled = at < 0 || at >= days.length - 1;
|
|
}
|
|
function go(step) {
|
|
const at = shown == null ? days.length - 1 : days.indexOf(shown);
|
|
const to = days[at + step];
|
|
if (to !== undefined) fetchIt(to);
|
|
}
|
|
older.addEventListener('click', () => go(-1));
|
|
newer.addEventListener('click', () => go(1));
|
|
pick.addEventListener('change', () => fetchIt(Number(pick.value)));
|
|
|
|
let current = null;
|
|
async function fetchIt(day) {
|
|
if (!ship) return;
|
|
refresh.disabled = true;
|
|
try {
|
|
days = await api.downlinkDays(ship.id);
|
|
// Refreshing (no day given) jumps to the latest; a stale choice falls back to it too.
|
|
shown = day != null && days.includes(day) ? day : null;
|
|
const got = await api.downlink(ship.id, shown);
|
|
const bytes = got.bytes;
|
|
shown = got.day === days[days.length - 1] ? null : got.day;
|
|
syncPicker();
|
|
current = { bytes, day: got.day };
|
|
const allZero = bytes.every((b) => b === 0);
|
|
meta.textContent = `SHIP #${ship.id} // FROM THE RUN OF DAY ${got.day} // ${bytes.length} BYTES`;
|
|
dl.disabled = false;
|
|
clear(body).append(
|
|
...(allZero ? [h('p', { class: 'warn' }, 'BUFFER IS ALL ZEROS: THE SHIP HAS NOT WRITTEN ANYTHING TO ITS DOWNLINK BUFFER.')] : []),
|
|
hexView(bytes).el,
|
|
h('p', { class: 'hint' }, 'OFFSETS ARE INTO THE 1 KB DOWNLINK BUFFER (SHIP RAM 1024-2047). MULTI-BYTE VALUES ARE LITTLE-ENDIAN.'),
|
|
);
|
|
} catch (e) {
|
|
current = null;
|
|
days = [];
|
|
shown = null;
|
|
syncPicker();
|
|
dl.disabled = true;
|
|
meta.textContent = '';
|
|
clear(body).append(
|
|
e.status === 404
|
|
? h('div', { class: 'nosignal' },
|
|
h('p', { class: 'big' }, 'NO SIGNAL'),
|
|
h('p', { class: 'dim' }, 'THIS SHIP HAS NOT TRANSMITTED YET. A SHIP\'S FIRST DOWNLINK ARRIVES AFTER ITS FIRST DAY IN THE BELT.'))
|
|
: h('p', { class: 'bad' }, `ERROR: ${e.message}`),
|
|
);
|
|
} finally {
|
|
refresh.disabled = false;
|
|
}
|
|
}
|
|
|
|
// Reloading the account too keeps the ship's status and the day current.
|
|
refresh.addEventListener('click', () => ctx.refresh().catch((e) => toast(e.message, 'err')));
|
|
dl.addEventListener('click', () => {
|
|
if (!current) return;
|
|
download(`ship-${ship.id}-day-${current.day}-downlink.bin`, current.bytes);
|
|
toast(`SAVED ${current.bytes.length} BYTES`);
|
|
});
|
|
fetchIt();
|
|
|
|
return h('section', { class: 'panel' },
|
|
h('div', { class: 'panel-title' }, 'DOWNLINK RECEIVER', h('span', { class: 'spacer' }), meta),
|
|
h('div', { class: 'toolbar pad' }, refresh, older, pick, newer, h('span', { class: 'spacer' }), dl),
|
|
body,
|
|
);
|
|
}
|