Add floating windows and btop system monitor

This commit is contained in:
SirBlob committed 2026-10-31 23:07:12 -04:00
1 parent fd0b3b20a2
commit 201a1e0914
23 files changed
+1049 -36

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
import { profile } from '#lib/content';
import type { BtopData, GithubSummary, RepoProcess } from '#lib/btop/types';
import { fetchContributionCalendar } from './contributions';
const cacheDuration = 60 * 60 * 1000;
const githubHeaders = { Accept: 'application/vnd.github+json', 'User-Agent': 'portfolio-website' };
interface GithubRepo {
name: string;
html_url: string;
language: string | null;
stargazers_count: number;
forks_count: number;
size: number;
pushed_at: string;
fork: boolean;
}
let cached: { data: BtopData; fetchedAt: number } | null = null;
export function githubUsername(): string | null {
const githubUrl = profile.socials.find((social) => social.name === 'GitHub')?.url;
return githubUrl?.match(/github\.com\/([^/]+)/)?.[1] ?? null;
}
async function fetchJson<T>(url: string): Promise<T> {
const response = await fetch(url, { headers: githubHeaders });
if (!response.ok) throw new Error(`GitHub returned ${response.status} for ${url}`);
return response.json();
}
async function fetchSummary(username: string): Promise<GithubSummary> {
const user = await fetchJson<{ html_url: string; public_repos: number; followers: number }>(
`https://api.github.com/users/${username}`
);
return { username, profileUrl: user.html_url, publicRepos: user.public_repos, followers: user.followers };
}
async function fetchRepos(username: string): Promise<RepoProcess[]> {
const repos = await fetchJson<GithubRepo[]>(`https://api.github.com/users/${username}/repos?per_page=100&sort=pushed`);
return repos
.filter((repo) => !repo.fork)
.map((repo) => ({
name: repo.name,
url: repo.html_url,
language: repo.language,
stars: repo.stargazers_count,
forks: repo.forks_count,
sizeKb: repo.size,
pushedAt: repo.pushed_at
}));
}
export async function getBtopData(): Promise<BtopData | null> {
if (cached && Date.now() - cached.fetchedAt < cacheDuration) return cached.data;
const username = githubUsername();
if (!username) return null;
try {
const [github, contributions, repos] = await Promise.all([
fetchSummary(username),
fetchContributionCalendar(username),
fetchRepos(username)
]);
const data = { github, contributions, repos };
cached = { data, fetchedAt: Date.now() };
return data;
} catch (error) {
console.error('btop data lookup failed:', error);
return cached?.data ?? null;
}
}
+49
View File
@@ -0,0 +1,49 @@
import type { ContributionCalendar, ContributionDay } from '#lib/btop/types';
const dayCellPattern = /data-date="(\d{4}-\d{2}-\d{2})" id="contribution-day-component-(\d+)-(\d+)" data-level="(\d)"/g;
const tooltipPattern = /for="contribution-day-component-(\d+-\d+)"[^>]*>(\d+|No) contributions? on/g;
const totalPattern = /([\d,]+)\s+contributions?\s+in the last year/;
function parseCounts(html: string): Map<string, number> {
const counts = new Map<string, number>();
for (const [, cellId, count] of html.matchAll(tooltipPattern)) {
counts.set(cellId, count === 'No' ? 0 : Number(count));
}
return counts;
}
function parseWeeks(html: string): ContributionDay[][] {
const counts = parseCounts(html);
const weeks: ContributionDay[][] = [];
for (const [, date, weekday, week, level] of html.matchAll(dayCellPattern)) {
const weekIndex = Number(week);
weeks[weekIndex] ??= [];
weeks[weekIndex][Number(weekday)] = { date, level: Number(level), count: counts.get(`${weekday}-${week}`) ?? 0 };
}
return weeks.map((days) => days.filter(Boolean));
}
function currentStreak(days: ContributionDay[]): number {
const today = new Date().toISOString().slice(0, 10);
const pastDays = days.filter((day) => day.date <= today);
if (pastDays.at(-1)?.count === 0) pastDays.pop();
let streak = 0;
for (let index = pastDays.length - 1; index >= 0 && pastDays[index].count > 0; index--) streak++;
return streak;
}
export async function fetchContributionCalendar(username: string): Promise<ContributionCalendar> {
const response = await fetch(`https://github.com/users/${username}/contributions`, {
headers: { 'User-Agent': 'portfolio-website' }
});
if (!response.ok) throw new Error(`GitHub contributions returned ${response.status}`);
const html = await response.text();
const weeks = parseWeeks(html);
const days = weeks.flat().sort((first, second) => first.date.localeCompare(second.date));
const total = Number(html.match(totalPattern)?.[1].replace(/,/g, '') ?? days.reduce((sum, day) => sum + day.count, 0));
const bestDay = days.reduce<ContributionDay | null>((best, day) => (!best || day.count > best.count ? day : best), null);
return { total, weeks, currentStreak: currentStreak(days), bestDay };
}