| 300 | } |
| 301 | |
| 302 | async function drawPanel(panel, request, token) { |
| 303 | const section = document.getElementById(`panel-${panel.id}`); |
| 304 | section.dataset.stale = 'true'; |
| 305 | |
| 306 | try { |
| 307 | const data = await request; |
| 308 | // A slower panel from a superseded render must never overwrite the current one. |
| 309 | if (token !== state.renderToken) return; |
| 310 | |
| 311 | if (panel.empty?.(data)) { |
| 312 | setState(section, 'empty', 'Nothing in this range.'); |
| 313 | drawTable(section, panel.table?.(data)); |
| 314 | return; |
| 315 | } |
| 316 | |
| 317 | if (panel.kind === 'stat') drawStat(section, panel.stat(data)); |
| 318 | else if (panel.kind === 'funnel') drawFunnel(section, panel.funnel(data)); |
| 319 | else drawChart(section, panel, panel.chart(data)); |
| 320 | |
| 321 | $(section, 'figure').textContent = panel.figure ? panel.figure(data) : ''; |
| 322 | drawTable(section, panel.table?.(data)); |
| 323 | setState(section, 'ready'); |
| 324 | } catch (err) { |
| 325 | if (token !== state.renderToken) return; |
| 326 | // One panel's failure is one panel's problem: the message lands in the |
| 327 | // panel, the rest of the page keeps its data. |
| 328 | setState(section, 'error', `Could not load this panel — ${err.message ?? err}`); |
| 329 | const chart = charts.get(panel.id); |
| 330 | if (chart) { |
| 331 | chart.destroy(); |
| 332 | charts.delete(panel.id); |
| 333 | } |
| 334 | } finally { |
| 335 | if (token === state.renderToken) section.dataset.stale = 'false'; |
| 336 | } |
| 337 | } |
| 338 | |
| 339 | // --------------------------------------------------------------------------- |
| 340 | // Rendering everything |