102 lines
4.7 KiB
JavaScript
102 lines
4.7 KiB
JavaScript
// Uplink form: choose a binary file (or type hex) and transmit it.
|
|
|
|
import { api } from './api.js';
|
|
import { h, clear, toast } from './dom.js';
|
|
import { hexView, parseHex } from './hexview.js';
|
|
|
|
export function uplinkView(ctx) {
|
|
const ship = ctx.ship;
|
|
const limit = ctx.state.info?.comm_bytes ?? 1024;
|
|
const open = ship && (ship.status === 'launching' || ship.status === 'active');
|
|
|
|
let bytes = null;
|
|
let label = '';
|
|
|
|
const preview = h('div', { class: 'uplink-preview' });
|
|
const meter = h('div', { class: 'meter', role: 'meter', 'aria-valuemin': '0', 'aria-valuemax': String(limit), 'aria-label': 'Message size' }, h('div', { class: 'fill' }));
|
|
const sizeText = h('p', { class: 'dim' }, `NO MESSAGE LOADED. LIMIT ${limit} BYTES.`);
|
|
const err = h('p', { class: 'form-error', role: 'alert' });
|
|
const send = h('button', { class: 'btn primary', type: 'button', disabled: true }, ship ? `TRANSMIT TO SHIP #${ship.id}` : 'NO SHIP');
|
|
|
|
function set(b, from) {
|
|
bytes = b;
|
|
label = from;
|
|
err.textContent = '';
|
|
const over = b.length > limit;
|
|
meter.classList.toggle('over', over);
|
|
meter.setAttribute('aria-valuenow', String(b.length));
|
|
meter.querySelector('.fill').style.setProperty('width', `${Math.min(100, (b.length / limit) * 100)}%`);
|
|
sizeText.textContent = `${from}: ${b.length} BYTES OF ${limit}${over ? ' -- TOO LARGE' : ''}`;
|
|
if (over) err.textContent = `MESSAGE EXCEEDS THE ${limit}-BYTE LINK. REMOVE ${b.length - limit} BYTES.`;
|
|
else if (b.length === 0) err.textContent = 'MESSAGE IS EMPTY.';
|
|
send.disabled = !open || over || b.length === 0;
|
|
clear(preview);
|
|
if (b.length) {
|
|
preview.append(hexView(b, { rows: 8 }).el);
|
|
if (b.length > 128) preview.append(h('p', { class: 'hint' }, `SHOWING THE FIRST 128 OF ${b.length} BYTES.`));
|
|
}
|
|
}
|
|
|
|
// ---- file chooser / drop zone ------------------------------------------
|
|
const file = h('input', { type: 'file', id: 'uplink-file', class: 'sr-only' });
|
|
const drop = h('label', { class: `drop ${open ? '' : 'disabled'}`, for: 'uplink-file' },
|
|
h('span', { class: 'drop-big' }, 'DROP A BINARY FILE HERE'),
|
|
h('span', { class: 'dim' }, 'or click to choose one'),
|
|
);
|
|
file.disabled = !open;
|
|
const takeFile = async (f) => {
|
|
if (!f) return;
|
|
if (f.size > 1 << 20) { err.textContent = 'THAT FILE IS FAR TOO LARGE FOR THE LINK.'; return; }
|
|
set(new Uint8Array(await f.arrayBuffer()), f.name.toUpperCase());
|
|
};
|
|
file.addEventListener('change', () => takeFile(file.files[0]));
|
|
for (const ev of ['dragenter', 'dragover']) drop.addEventListener(ev, (e) => { e.preventDefault(); if (open) drop.classList.add('over'); });
|
|
for (const ev of ['dragleave', 'drop']) drop.addEventListener(ev, () => drop.classList.remove('over'));
|
|
drop.addEventListener('drop', (e) => { e.preventDefault(); if (open) takeFile(e.dataTransfer.files[0]); });
|
|
|
|
// ---- hex entry ---------------------------------------------------------
|
|
const hexIn = h('textarea', { id: 'hex-in', rows: '3', spellcheck: 'false', placeholder: 'DE AD BE EF 00 01 ...', disabled: !open, 'aria-label': 'Message as hexadecimal' });
|
|
const useHex = h('button', { class: 'btn small', type: 'button', disabled: !open, onclick: () => {
|
|
try { set(parseHex(hexIn.value), 'HEX ENTRY'); } catch (e) { err.textContent = `HEX ERROR: ${e.message}`; }
|
|
} }, 'USE HEX');
|
|
|
|
send.addEventListener('click', async () => {
|
|
send.disabled = true;
|
|
try {
|
|
await api.setUplink(ship.id, bytes);
|
|
toast(`UPLINK QUEUED FOR SHIP #${ship.id}: ${bytes.length} BYTES. DELIVERED AT THE START OF THE NEXT RUN.`);
|
|
} catch (e) {
|
|
err.textContent = `TRANSMISSION FAILED: ${e.message}`;
|
|
send.disabled = false;
|
|
}
|
|
});
|
|
|
|
const status = !ship
|
|
? 'YOU HAVE NO SHIP.'
|
|
: open
|
|
? `TARGET: SHIP #${ship.id}. A NEW UPLINK REPLACES ONE ALREADY QUEUED.`
|
|
: `SHIP #${ship.id} IS ${ship.status.toUpperCase()}. UPLINKS CAN ONLY BE SENT TO SHIPS THAT ARE LAUNCHING OR IN THE BELT.`;
|
|
|
|
return h('div', { class: 'grid-2 uplink-grid' },
|
|
h('section', { class: 'panel' },
|
|
h('div', { class: 'panel-title' }, 'UPLINK TRANSMITTER'),
|
|
h('div', { class: 'panel-body stack' },
|
|
h('p', { class: open ? 'hint' : 'warn' }, status),
|
|
drop, file,
|
|
h('label', { for: 'hex-in' }, 'OR ENTER HEX'),
|
|
hexIn,
|
|
h('div', null, useHex),
|
|
meter, sizeText, err,
|
|
h('div', null, send),
|
|
),
|
|
),
|
|
h('section', { class: 'panel' },
|
|
h('div', { class: 'panel-title' }, 'OUTGOING MESSAGE'),
|
|
h('div', { class: 'panel-body' },
|
|
preview,
|
|
h('p', { class: 'hint' }, 'THE MESSAGE IS OPAQUE BYTES. YOUR SHIP\'S PROGRAM DECIDES WHAT IT MEANS. IT ARRIVES IN THE UPLINK BUFFER (RAM 0-1023) BEFORE THE FIRST TICK OF THE NEXT DAILY RUN.'),
|
|
),
|
|
),
|
|
);
|
|
}
|