Green squares for your time on glitch

if you’ve downloaded your glitch stuff (Get_my_glitch_stuff.sh), here’s some files to generate something like that grid of green squares they have on GitHub to show which days you did stuff. except it’s using data from Glitch’s rewind system.

first, there’s this extract_rewind_timestamps.sh. run this to create rewind_timestamps.txt

#!/bin/sh -eu
project_domains() {
	tail -n+2 projects.txt | cut -d' ' -f1
}
if [ ! -e user_id.txt ]; then
  snail whoami -n >user_id.txt
fi
user_id=$(cat user_id.txt)
mkdir -p /tmp/inspectgit
project_domains | while read -r domain; do
  rm -rf /tmp/inspectgit/app
	echo >&2 "reading $domain"
	tar -xf "projects/$domain/app.tar.gz" -C /tmp/inspectgit app/.git || continue
	git -C /tmp/inspectgit/app/.git log --branches --author=Glitch --grep=":$user_id/" --format="%H %at" || true
done | sort -k1,1 -u >rewind_timestamps.txt
rm -rf /tmp/inspectgit

then there’s this render_rewind_timestamps.html to total up the per-day rewind checkpoints and draw the squares.

<!doctype html>
<style>
	body {
		font-family: sans-serif;
	}
	.year-label {
		margin: 1rem 0rem 0rem;
		font-weight: normal;
	}
	.squares {
		display: flex;
		flex-direction: column;
		flex-wrap: wrap;
		align-content: flex-start;
		height: 91px;
	}
	.day {
		display: block;
		margin: 0px 3px 3px 0px;
		border-radius: 2px;
		width: 10px;
		height: 10px;
	}
	.zero {
		border: 1px solid hsl(0 0% 90%);
		width: 8px;
		height: 8px;
	}
</style>
<form id="timestamps_form"><input id="timestamps_input" type="file"> <input type="submit" value="render"></form>
<p id="out"></p>
<script>
	function dayFromTimestamp(/** @type {number} */ timestamp) {
		const d = new Date(timestamp * 1000);
		d.setHours(12, 0, 0, 0);
		return Math.round(d.getTime() / 86400000);
	}
	function dateFromDay(/** @type {number} */ day) {
		return new Date(day * 86400000);
	}
	const /** @type {HTMLFormElement} */ timestampsForm = document.getElementById('timestamps_form');
	const /** @type {HTMLInputElement} */ timestampsInput = document.getElementById('timestamps_input');
	const /** @type {HTMLParagraphElement} */ out = document.getElementById('out');
	timestampsForm.onsubmit = (e) => {
		e.preventDefault();
		out.innerHTML = '';
		if (!timestampsInput.files.length) return;
		(async () => {
			try {
				const timestampsStr = await timestampsInput.files[0].text();
				let /** @type {number | null} */ earliestDay = null;
				let /** @type {number | null} */ latestDay = null;
				const countByDay = {};
				for (const line of timestampsStr.split('\n')) {
					if (!line.trim()) continue;
					const [hash, timestampStr] = line.split(' ');
					const timestamp = +timestampStr;
					const day = dayFromTimestamp(timestamp);
					if (earliestDay === null || day < earliestDay) {
						earliestDay = day;
					}
					if (latestDay === null || day > latestDay) {
						latestDay = day;
					}
					if (day in countByDay) {
						countByDay[day]++;
					} else {
						countByDay[day] = 1;
					}
				}
				let maxCount = 0;
				for (const day in countByDay) {
					if (countByDay[day] > maxCount) {
						maxCount = countByDay[day];
					}
				}
				let /** @type {number | null} */ year = null;
				let /** @type {HTMLDivElement | null} */ squaresDiv = null;
				for (let day = earliestDay; day <= latestDay; day++) {
					const d = dateFromDay(day);
					const dayYear = d.getFullYear();
					if (year === null || dayYear !== year) {
						year = dayYear;
						const yearDiv = document.createElement('div');
						yearDiv.className = 'year';
						const yearLabelHeading = document.createElement('h2');
						yearLabelHeading.className = 'year-label';
						yearLabelHeading.textContent = year;
						yearDiv.appendChild(yearLabelHeading);
						squaresDiv = document.createElement('div');
						squaresDiv.className = 'squares';
						yearDiv.appendChild(squaresDiv);
						out.appendChild(yearDiv);
						const firstDateOfYear = new Date(dayYear, 0, 1, 12);
						const firstDayOfWeek = firstDateOfYear.getDay();
						for (let i = 0; i < firstDayOfWeek; i++) {
							const daySpan = document.createElement('span');
							daySpan.className = 'day padding';
							squaresDiv.appendChild(daySpan);
						}
						const skippedDays = Math.round((d.getTime() - firstDateOfYear.getTime()) / 86400000);
						for (let i = 0; i < skippedDays; i++) {
							const daySpan = document.createElement('span');
							daySpan.className = 'day zero';
							squaresDiv.appendChild(daySpan);
						}
					}
					const count = day in countByDay ? countByDay[day] : 0;
					const l = count ? 85 - 60 * (count / maxCount) : 100;
					const daySpan = document.createElement('span');
					daySpan.className = count ? 'day' : 'day zero';
					daySpan.title = `${count} ${count === 1 ? 'checkpoint' : 'checkpoints'} on ${d.toDateString()}`;
					daySpan.style.backgroundColor = `hsl(135 70% ${l}%)`;
					squaresDiv.appendChild(daySpan);
				}
				if (year !== null) {
					for (let day = latestDay + 1; true; day++) {
						const d = dateFromDay(day);
						if (d.getFullYear() !== year) break;
						const daySpan = document.createElement('span');
						daySpan.className = 'day zero';
						squaresDiv.appendChild(daySpan);
					}
				}
			} catch (e) {
				reportError(e);
			}
		})();
	};
</script>

here’s mine

9 Likes

this is super cool

2 Likes

wait what’s with that 8-day week in 2025

:laughing: flex layout is such a slippery thing

edit: oh my god that’s not an 8 day week, every other column had been 6 days :skull:

3 Likes

i was working on my dissertation defense and made some deals to get an extra day there

3 Likes

1 Like