claude-usage-widget/src/components/BlockRing.svelte

77 lines
2.2 KiB
Svelte

<script lang="ts">
import type { BlockSummary } from "../types";
import { formatTokens, formatCountdown, formatPct } from "../format";
let {
block,
cap,
nowSeconds,
}: {
block: BlockSummary | null;
cap: number;
/** Locally-decremented countdown so we don't IPC just to tick the clock. */
nowSeconds: number;
} = $props();
// Geometry.
const SIZE = 140;
const STROKE = 12;
const R = (SIZE - STROKE) / 2;
const C = 2 * Math.PI * R;
let pct = $derived(block && cap > 0 ? Math.min(1, block.total_tokens / cap) : 0);
let dash = $derived(C * pct);
let color = $derived(
pct > 0.95 ? "var(--danger)" : pct > 0.8 ? "var(--warn)" : "var(--accent)",
);
let pulse = $derived(pct > 0.9);
</script>
<div class="wrap">
<svg width={SIZE} height={SIZE} viewBox={`0 0 ${SIZE} ${SIZE}`}>
<!-- track -->
<circle
cx={SIZE / 2}
cy={SIZE / 2}
r={R}
fill="none"
stroke="var(--border)"
stroke-width={STROKE}
/>
<!-- progress -->
<circle
cx={SIZE / 2}
cy={SIZE / 2}
r={R}
fill="none"
stroke={color}
stroke-width={STROKE}
stroke-linecap="round"
stroke-dasharray={`${dash} ${C}`}
transform={`rotate(-90 ${SIZE / 2} ${SIZE / 2})`}
class:pulse
/>
<text x={SIZE / 2} y={SIZE / 2 - 6} text-anchor="middle" class="big">
{block ? formatTokens(block.total_tokens) : "—"}
</text>
<text x={SIZE / 2} y={SIZE / 2 + 12} text-anchor="middle" class="small">
{block ? formatPct(block.total_tokens, cap) : ""}
</text>
<text x={SIZE / 2} y={SIZE / 2 + 30} text-anchor="middle" class="countdown">
{block ? formatCountdown(nowSeconds) : "no activity"}
</text>
</svg>
</div>
<style>
.wrap { display: flex; justify-content: center; padding: 14px 0 6px; }
text { fill: var(--fg); font-family: inherit; }
text.big { font-size: 22px; font-weight: 600; }
text.small { font-size: 11px; fill: var(--fg-dim); }
text.countdown { font-size: 11px; fill: var(--fg-dim); font-variant-numeric: tabular-nums; }
.pulse { animation: pulse 1.6s ease-in-out infinite; }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.55; }
}
</style>