1
  2const blockSeconds = 12 * 3600; // must match config.GraphBlockSize.Seconds()
  3
  4var tileWidth = 1024;
  5var tileHeight = 288;
  6
  7const timeStep = 2 * 3600; // one label every 2 hours
  8
  9const isMobile = window.matchMedia("(hover: none) and (pointer: coarse)").matches;
 10if (isMobile) {
 11  tileWidth *= 4;
 12  tileHeight *= 4;
 13}
 14
 15const track = document.getElementById("graph-track");
 16const scroll = document.getElementById("graph-scroll");
 17
 18let loaded = new Set();
 19
 20let centerTime = Math.floor(Date.now()/1000);
 21
 22// round to block boundary
 23function snap(t) {
 24    return Math.floor(t / blockSeconds) * blockSeconds;
 25}
 26
 27
 28function formatLocalTime(unix) {
 29    return new Date(unix * 1000).toLocaleString(undefined, {
 30        day: "2-digit",
 31        month: "2-digit",
 32        hour: "2-digit",
 33        minute: "2-digit",
 34        hour12: false
 35    });
 36}
 37
 38function getBlockTimestamp(block)
 39{
 40    let img = block.querySelector(".graph-tile");
 41
 42    return Number(
 43        img.src
 44        .split("/")
 45        .pop()
 46        .split(".")[0]
 47    );
 48}
 49
 50function addBlock(timestamp, before=false)
 51{
 52    timestamp = snap(timestamp);
 53
 54    if(loaded.has(timestamp))
 55        return;
 56
 57    loaded.add(timestamp);
 58
 59
 60    let block = document.createElement("div");
 61    block.className = "graph-block";
 62
 63
 64    let img = document.createElement("img");
 65
 66    img.className = "graph-tile";
 67
 68    img.width = tileWidth;
 69    img.height = tileHeight;
 70
 71    img.src =
 72        `https://caffeine.ontake.dev/graph/${timestamp}.png`;
 73
 74
 75  block.appendChild(img);
 76
 77
 78    // add time labels
 79    for(let t = timestamp; t < timestamp + blockSeconds; t += timeStep)
 80    {
 81        let label = document.createElement("div");
 82
 83        label.className = "time-label";
 84
 85        let x =
 86            ((t - timestamp) / blockSeconds) * tileWidth;
 87
 88
 89        label.style.left = `${x}px`;
 90
 91
 92        label.textContent = formatLocalTime(t);
 93
 94
 95        block.appendChild(label);
 96    }
 97
 98
 99    if(before)
100        track.prepend(block);
101    else
102        track.appendChild(block);
103}
104
105
106function loadAround(t)
107{
108    // Older tiles: add in reverse chronological order
109    for (let i = -1; i >= -3; i--)
110    {
111        addBlock(
112            t + i * blockSeconds,
113            true
114        );
115    }
116
117    // Current + future tiles
118    for (let i = 0; i <= 3; i++)
119    {
120        addBlock(
121            t + i * blockSeconds,
122            false
123        );
124    }
125}
126
127
128loadAround(centerTime);
129
130
131// initial position
132setTimeout(()=>{
133
134    scroll.scrollLeft =
135        scroll.scrollWidth/2 - scroll.clientWidth/2;
136
137},100);
138
139
140
141scroll.addEventListener(
142"scroll",
143()=>{
144
145
146    let left =
147        scroll.scrollLeft;
148
149
150    let max =
151        scroll.scrollWidth -
152        scroll.clientWidth;
153
154
155
156    // near left edge
157    if(left < tileWidth / 2)
158    {
159        let first =
160            track.firstChild;
161
162
163        if(first)
164        {
165            let ts = getBlockTimestamp(first);
166
167            let oldWidth = scroll.scrollWidth;
168
169            addBlock(ts - blockSeconds, true);
170
171            requestAnimationFrame(() => {
172                let newWidth = scroll.scrollWidth;
173                scroll.scrollLeft += newWidth - oldWidth;
174            });
175        }
176    }
177
178
179
180    // near right edge
181    if(max-left < tileWidth / 2)
182    {
183
184        let last =
185            track.lastChild;
186
187
188        if(last)
189        {
190          let ts = getBlockTimestamp(last);
191
192
193            addBlock(
194                ts+blockSeconds
195            );
196        }
197
198    }
199
200
201});