Files
omarchy/plugins/io.github.x3me.nexthop/pathspark.js
T
asepharyana 1cdb82a76f Sync config from arch
- hypr/apps.lua
- hypr/autostart.lua
- hypr/envs.lua
- hypr/hyprland.lua
- hypr/hyprsunset.conf
- hypr/input.lua
- hypr/looknfeel.lua
- hypr/omasettings.lua
- hypr/xdph.conf
- omarchy/branding/about.txt
- omarchy/branding/screensaver.txt
- omarchy/extensions/omarchy-menu.jsonc
- omarchy/hooks/battery-low.d/play-warning-sound.sample
- omarchy/hooks/font-set.d/show-font-notification.sample
- omarchy/hooks/post-boot.d/weather.sample
- omarchy/hooks/post-update.d/install-voxtype.hook
- omarchy/hooks/post-update.d/setup-agent.hook
- omarchy/hooks/post-update.d/setup-fingerprint.hook
- omarchy/hooks/post-update.d/show-update-notification.sample
- omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample
- omarchy/hooks/theme-set.d/show-theme-notification.sample
- omarchy/shell.json
- omarchy/shell.toml
- omarchy/theme.name
- omarchy/themes/azure-glow/README.md
- omarchy/themes/azure-glow/alacritty.toml
- omarchy/themes/azure-glow/btop.theme
- omarchy/themes/azure-glow/hyprland.conf
- omarchy/themes/azure-glow/hyprlock.conf
- omarchy/themes/azure-glow/icons.theme
- … 269 more
2026-09-23 15:19:12 +07:00

205 lines
8.0 KiB
JavaScript

// The recent history drawn into the path connectors on the Overview.
//
// The two lines between the nodes were flat 2 px bars coloured by the current
// value. They already occupied this space, so showing three minutes of each
// leg there costs no new row — the constraint that decides most of this
// panel's layout.
//
// What this file owns is slot derivation and the paint. It owns no arithmetic
// about the legs: the ISP leg is subtracted in the daemon (`score.wan_point_ms`)
// and arrives per point, because the rule that matters there is the one that
// REFUSES to answer when a gateway reads slower than the internet behind it,
// and a second copy of a refusal is the copy that forgets. Colours come in as
// a function so `PathChain.legColor` stays the only place thresholds live.
//
// No `.pragma library`: it would drop the QML context and `Qt.rgba` with it.
// 36 slots of 5 s is three minutes. recent.json holds 360 of them, but the
// Latency tab already draws the whole half hour; a second long window here
// would be the same chart twice. Three minutes is what changes while you
// watch, which is the point of putting it here.
var SLOTS = 36;
// Both legs share one zero-based scale at least this tall, so a wobble on a
// 2 ms local leg cannot be drawn larger than a slower WAN. The cost is that a
// fast local leg is a flat line near the floor. That is honest: it IS flat.
var SCALE_FLOOR_MS = 50;
// The ring is the widest thing drawn, so it — not the line — sets the
// margins. Everything below is derived from it: a plot inset by less than
// the ring's outer edge cuts the ring, and the newest point is exactly
// where the ring goes, which is exactly the right edge. That is how the
// first cut shipped: the line was laid out edge to edge, correctly for a
// line, and the ring inherited an inset of zero.
// [Plamen, 2026-09-12: "its a bit cut off the pulsing dot?"]
var DOT_R = 2;
var RING_R = 4.5;
var RING_STROKE = 1.2;
// The arc's outer edge, which is what must stay inside the canvas.
var PAD = RING_R + RING_STROKE / 2 + 0.4;
// Clearance between the lowest a line may sit and the down bar, so a
// value at zero is not read as an outage marker.
var DOWN_GAP = 2;
/**
* One leg's last `n` slots, newest last.
*
* Three outcomes, and keeping them apart is the whole job:
* {v: ms} measured
* {down: true} probes went out and nothing came back
* null nothing to say — no probe was sent, or the figure is withheld
*
* `loss === null` is the daemon's way of saying it sampled nothing in that
* bucket, which is a gap rather than an outage. A bucket that sampled and got
* no reply carries a loss figure with no `total`, and that is down.
*/
function slots(points, key, n) {
var out = [];
var pts = points || [];
var from = Math.max(0, pts.length - (n || SLOTS));
for (var i = from; i < pts.length; i++) {
var p = pts[i];
if (!p || p.loss === null || p.loss === undefined) { out.push(null); continue; }
if (key === "local") {
// The router itself did not answer.
if (p.local === null || p.local === undefined) { out.push({ down: true }); continue; }
out.push({ v: p.local });
continue;
}
// Nothing beyond the router answered at all.
if (p.total === null || p.total === undefined) { out.push({ down: true }); continue; }
// It answered, but the subtraction was withheld — unknown, not zero,
// and certainly not an outage.
if (p.wan === null || p.wan === undefined) { out.push(null); continue; }
out.push({ v: p.wan });
}
while (out.length < (n || SLOTS)) out.unshift(null);
return out;
}
/** The tallest measured value across every leg, floored. */
function sharedMax(seriesList, floor) {
var m = floor === undefined ? SCALE_FLOOR_MS : floor;
for (var i = 0; i < seriesList.length; i++) {
var s = seriesList[i] || [];
for (var j = 0; j < s.length; j++) {
if (s[j] && !s[j].down && s[j].v > m) m = s[j].v;
}
}
return m;
}
/** The newest slot that carries anything at all, or -1. */
function newestIndex(series) {
for (var i = series.length - 1; i >= 0; i--) if (series[i]) return i;
return -1;
}
/**
* Paint one leg.
*
* `opts`: { max, phase, live, motion, colorFor, downColor, dimColor }
* `phase` runs 0..1 and drives the ring; `colorFor(ms, down)` is the panel's
* own `legColor`, passed in rather than reimplemented.
*/
function draw(ctx, w, h, series, opts) {
ctx.clearRect(0, 0, w, h);
if (!series || series.length === 0 || w <= 0 || h <= 0) return;
var max = opts.max || SCALE_FLOOR_MS;
// One margin on all four sides, so a ring fits wherever a mark can
// land: at the top of the scale, on the down bar, or at either end.
var downY = h - PAD;
var plotTop = PAD;
var plotH = Math.max(1, downY - DOWN_GAP - plotTop);
var left = PAD, right = w - PAD;
var step = series.length > 1 ? (right - left) / (series.length - 1) : 0;
function yOf(v) { return plotTop + plotH - (Math.min(v, max) / max) * plotH; }
// The measured line, cut at every gap so nothing is ever drawn across one.
var run = [];
function flushLine() {
if (run.length > 1) {
ctx.beginPath();
for (var i = 0; i < run.length; i++) {
if (i === 0) ctx.moveTo(run[i].x, run[i].y);
else ctx.lineTo(run[i].x, run[i].y);
}
ctx.strokeStyle = run[run.length - 1].c;
ctx.lineWidth = 1.5;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.stroke();
} else if (run.length === 1) {
ctx.fillStyle = run[0].c;
ctx.fillRect(run[0].x - 0.75, run[0].y - 0.75, 1.5, 1.5);
}
run = [];
}
// A run of down slots is ONE outage, so it is one bar along the bottom
// rather than a row of ticks: its length is the duration.
var downRun = null;
function flushDown() {
if (!downRun) return;
ctx.fillStyle = opts.downColor;
ctx.fillRect(downRun.x0 - 1, downY - 1.5,
Math.max(2, downRun.x1 - downRun.x0 + 2), 3);
downRun = null;
}
for (var i = 0; i < series.length; i++) {
var p = series[i], x = left + i * step;
if (!p) { flushLine(); flushDown(); continue; }
if (p.down) {
flushLine();
if (downRun) downRun.x1 = x; else downRun = { x0: x, x1: x };
continue;
}
flushDown();
run.push({ x: x, y: yOf(p.v), c: opts.colorFor(p.v, false) });
}
flushLine();
flushDown();
// The ring goes on the LAST SLOT DRAWN, whatever kind it is.
//
// A down sample is a measurement: a probe went out and nothing answered,
// which is a reading arriving. So it pulses, in red, like any other newest
// mark. Only a gap gets no ring, because nothing arrived to claim. The
// reference implementation never pulses a down marker, and it strands the
// ring back at the last measured point while the line's real end sits
// somewhere else, which reads as a rendering fault.
// [D, Plamen, 2026-09-12]
var last = series.length - 1;
if (!series[last]) return;
var lx = left + last * step;
var ly = series[last].down ? downY : yOf(series[last].v);
var col = series[last].down ? opts.downColor
: opts.colorFor(series[last].v, false);
if (opts.live) {
ctx.beginPath();
if (opts.motion) {
ctx.arc(lx, ly, DOT_R + opts.phase * (RING_R - DOT_R), 0, Math.PI * 2);
ctx.globalAlpha = 0.5 * (1 - opts.phase);
} else {
// The claim is still made, without motion.
ctx.arc(lx, ly, RING_R, 0, Math.PI * 2);
ctx.globalAlpha = 0.4;
}
ctx.strokeStyle = col;
ctx.lineWidth = RING_STROKE;
ctx.stroke();
ctx.globalAlpha = 1;
}
ctx.beginPath();
ctx.arc(lx, ly, DOT_R, 0, Math.PI * 2);
ctx.fillStyle = col;
ctx.fill();
}