//+------------------------------------------------------------------+ //| app.js | //| Interfaz: carga el .bin, muestra la ficha del evento y la tabla | //| de valores con lista virtual (solo se dibujan las filas cerca | //| de la pantalla; el resto se lee del buffer cuando se hace scroll)| //+------------------------------------------------------------------+ (function () { 'use strict'; const ROW_H = 28; // alto fijo de fila (px): permite calcular que filas ver sin medirlas const RENDER_SCREENS = 4; // se dibujan ~4 pantallas de filas alrededor de la vista, el resto on-demand const $ = (id) => document.getElementById(id); const el = { file: $('file'), drop: $('drop'), msg: $('msg'), info: $('info'), title: $('title'), sub: $('sub'), chips: $('chips'), stats: $('stats'), warn: $('warn'), tableWrap: $('tableWrap'), scroller: $('scroller'), body: $('body'), rows: $('rows'), order: $('order'), count: $('count') }; let H = null; // cabecera parseada let desc = true; // true = mas reciente primero let start = 0, end = 0; // rango de filas dibujado [start, end) let ticking = false; //--- Utilidades function esc(s) { return String(s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); } function setMsg(text, isErr) { el.msg.textContent = text; el.msg.className = isErr ? 'err' : ''; } //--- Carga del archivo async function loadFile(file) { if (!file) return; const t0 = performance.now(); try { const buf = await file.arrayBuffer(); H = CalFmt.parseHeader(buf); } catch (e) { H = null; el.info.hidden = true; el.tableWrap.hidden = true; setMsg('No se pudo leer "' + file.name + '": ' + e.message, true); return; } setMsg(file.name + ' — ' + (file.size / 1024).toFixed(1) + ' KB, cabecera leida en ' + (performance.now() - t0).toFixed(1) + ' ms', false); showInfo(file); resetTable(); } function chip(k, v) { return '' + esc(k) + ' ' + esc(v) + ''; } function showInfo(file) { const C = CalFmt; el.title.textContent = H.name || '(sin nombre)'; let sub = esc(H.code); if (/^https?:\/\//i.test(H.url)) sub += ' · ' + esc(H.url) + ''; else if (H.url) sub += ' · ' + esc(H.url); el.sub.innerHTML = sub; el.chips.innerHTML = [ chip('Pais', (C.COUNTRY[H.countryId] || 'Desconocido') + ' (' + H.countryId + ')'), chip('Importancia', C.label(C.IMPORTANCE, H.importance)), chip('Tipo', C.label(C.TYPE, H.type)), chip('Sector', C.label(C.SECTOR, H.sector)), chip('Frecuencia', C.label(C.FREQUENCY, H.frequency)), chip('Hora', C.label(C.TIMEMODE, H.timeMode)), chip('Unidad', C.label(C.UNIT, H.unit)), chip('Multiplicador', C.label(C.MULT_NAME, H.multiplier)), chip('Decimales', H.digits), chip('ID evento', H.id) ].join(''); let first = '—', last = '—'; if (H.count > 0) { first = C.fmtTime(C.readRow(H, 0).time); last = C.fmtTime(C.readRow(H, H.count - 1).time); } el.stats.textContent = H.count.toLocaleString() + ' registros · ' + first + ' → ' + last + ' (hora del servidor)'; el.warn.hidden = !H.warning; el.warn.textContent = H.warning || ''; el.info.hidden = false; } //--- Tabla virtual function resetTable() { el.tableWrap.hidden = H.count === 0; el.count.textContent = H.count.toLocaleString() + ' filas'; el.body.style.height = H.count * ROW_H + 'px'; el.scroller.scrollTop = 0; start = end = 0; el.rows.innerHTML = ''; render(true); } function visibleRows() { return Math.max(1, Math.ceil(el.scroller.clientHeight / ROW_H)); } function rowHtml(k) { const C = CalFmt; const i = desc ? H.count - 1 - k : k; // k = posicion en pantalla, i = indice en el archivo const r = C.readRow(H, i); const imp = r.impact === 1 ? 'pos' : (r.impact === 2 ? 'neg' : ''); return '