File size: 3,605 Bytes
38adf92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
const t = document.getElementById('t'), z = document.getElementById('z'),
      grid = document.getElementById('grid'),
      channelsBox = document.getElementById('channels'), scalesBox = document.getElementById('scales');
const CHANNELS = META.channels, TVALS = META.t, LEVELS = META.levels;
const SCALES = [...META.scales].reverse();
let selected = [CHANNELS[0]], scale = SCALES[0], AR = 1;

function button(label) {
  const b = document.createElement('button');
  b.type = 'button';
  b.textContent = label;
  return b;
}

const chanButtons = CHANNELS.map(ch => {
  const b = button(ch);
  b.onclick = () => {
    if (selected.includes(ch)) { if (selected.length > 1) selected = selected.filter(x => x !== ch); }
    else selected.push(ch);
    refreshChannels();
    rebuild();
  };
  channelsBox.appendChild(b);
  return b;
});
function refreshChannels() {
  CHANNELS.forEach((ch, i) => chanButtons[i].setAttribute('aria-pressed', selected.includes(ch)));
}

scalesBox.setAttribute('role', 'radiogroup');
scalesBox.setAttribute('aria-label', 'colour scale');
const scaleButtons = SCALES.map((sc, i) => {
  const b = button(sc);
  b.setAttribute('role', 'radio');
  b.onclick = () => selectScale(i);
  b.onkeydown = e => {
    const d = e.key === 'ArrowRight' || e.key === 'ArrowDown' ? 1
            : e.key === 'ArrowLeft' || e.key === 'ArrowUp' ? -1 : 0;
    if (!d) return;
    e.preventDefault();
    selectScale((i + d + SCALES.length) % SCALES.length);
  };
  scalesBox.appendChild(b);
  return b;
});
function refreshScales() {
  SCALES.forEach((sc, i) => {
    const on = sc === scale;
    scaleButtons[i].setAttribute('aria-checked', on);
    scaleButtons[i].tabIndex = on ? 0 : -1;
  });
}
function selectScale(i) {
  scale = SCALES[i];
  refreshScales();
  scaleButtons[i].focus();
  update();
}

function update() {
  const frame = TVALS[+t.value], level = +z.value;
  grid.querySelectorAll('img').forEach(im =>
    im.src = `cgi-bin/index.cgi?s=${scale}&c=${im.dataset.ch}&t=${frame}&z=${level}`);
}

function fit() {
  const n = selected.length;
  if (!n) return;
  const gap = parseFloat(getComputedStyle(grid).gap) || 0;
  const top = grid.getBoundingClientRect().top;
  const W = grid.clientWidth, H = window.innerHeight - top;
  let best = { cols: 1, w: 0 };
  for (let cols = 1; cols <= n; cols++) {
    const rows = Math.ceil(n / cols);
    const w = Math.min((W - (cols - 1) * gap) / cols, ((H - (rows - 1) * gap) / rows) * AR);
    if (w > best.w) best = { cols, w };
  }
  grid.style.gridTemplateColumns = `repeat(${best.cols}, ${Math.floor(best.w)}px)`;
}

function rebuild() {
  grid.replaceChildren(...selected.map(ch => {
    const im = document.createElement('img');
    im.dataset.ch = ch;
    im.alt = ch;
    im.onerror = () => { im.alt = ch + ' — failed to load'; };
    return im;
  }));
  const first = grid.querySelector('img');
  if (first) first.onload = () => { AR = first.naturalWidth / first.naturalHeight; fit(); };
  update();
  fit();
}

t.max = TVALS.length - 1;
z.max = LEVELS.length - 1;
t.addEventListener('input', update);
z.addEventListener('input', update);
window.addEventListener('resize', fit);

let active = t;
[t, z].forEach(s => s.addEventListener('focus', () => active = s));
addEventListener('keydown', e => {
  if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
  if (document.activeElement && document.activeElement.tagName === 'BUTTON') return;
  e.preventDefault();
  e.key === 'ArrowLeft' ? active.stepDown() : active.stepUp();
  active.dispatchEvent(new Event('input'));
});

refreshChannels();
refreshScales();
rebuild();