Files
reforgermods-launcher/src/components/HistoryChart.tsx
T
SowinskiBraedenandClaude Opus 5 377f3a31b3 initial commit: reforgermods.net launcher 0.1.0
A desktop server browser for Arma Reforger, built on the reforgermods.net v2
API. Live server list with search, sort and filters; a detail inspector with
population history and mod readiness; measured ICMP latency; favorites, recents
and settings; and direct joining through Steam.

Tauri 2 and Rust, with a Preact frontend that builds to 80 KB of JavaScript in
one chunk. Logic lives in rfm-core, which has no Tauri dependency and is tested
directly; src-tauri is the window, the command surface and the wiring.

Packaged for the public beta: a per-user NSIS installer, a portable .exe and an
Arch package, with signature-verified background updates on Windows and a
release pipeline that builds from a tag. The artifacts are not yet
Authenticode-signed — that is wired and waiting on an Azure Trusted Signing
identity, and docs/distribution.md says so plainly rather than letting anyone
discover it at the SmartScreen dialog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 20:29:00 -07:00

75 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Population history for the selected server.
*
* One inline SVG, no charting library: a min–max band, an average line, and a
* range switcher. The paths come from `lib/chart.ts`, which is pure and tested.
*/
import { historyLoading, historyRange, historySeries, setHistoryRange } from "../lib/state";
import { buildChart } from "../lib/chart";
import { HISTORY_RANGES } from "../lib/types";
/** Drawing box. `preserveAspectRatio="none"` stretches it to the panel width. */
const WIDTH = 300;
const HEIGHT = 56;
export function HistoryChart() {
const series = historySeries.value;
const loading = historyLoading.value;
const range = historyRange.value;
const geometry = series ? buildChart(series.points, WIDTH, HEIGHT) : null;
return (
<div class="section">
<div class="section__h">
<span>Population</span>
<div class="rangeswitch">
{HISTORY_RANGES.map((option) => (
<button
key={option.value}
data-on={option.value === range}
onClick={() => setHistoryRange(option.value)}
>
{option.label}
</button>
))}
</div>
</div>
{geometry ? (
<>
<svg
class="chart"
viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
preserveAspectRatio="none"
role="img"
aria-label={`Population over the last ${range}: peak ${geometry.peak}, average ${geometry.mean}`}
>
{/* Band first so the average line sits on top of it. */}
<path class="chart__band" d={geometry.band} />
{/* non-scaling-stroke keeps the line 1px despite the stretched viewBox. */}
<path class="chart__line" d={geometry.line} vector-effect="non-scaling-stroke" />
</svg>
<div class="chart__legend">
<span>
peak <b>{geometry.peak}</b>
</span>
<span>
avg <b>{geometry.mean}</b>
</span>
<span>
low <b>{geometry.low}</b>
</span>
{geometry.cap > 0 && <span>of {geometry.cap}</span>}
<span class="chart__bucket">{series?.bucket} buckets</span>
</div>
</>
) : (
<p class="state__hint" style={{ margin: 0 }}>
{loading ? "Loading history…" : "No recorded history for this range."}
</p>
)}
</div>
);
}