Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public/*html
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
html:
cd scripts && uv run gen_web_views.py
Empty file added public/css/style.css
Empty file.
59 changes: 59 additions & 0 deletions scripts/gen_web_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# /// script
# dependencies = [
# "jinja2",
# "numpy",
# "pandas",
# ]
# ///

import sys
import jinja2
import json
import numpy as np
import pandas as pd
from jinja2 import Environment, FileSystemLoader
from pathlib import Path

BASE = Path(__file__).parents[0]
DEST = BASE / ".." / "public"
DATA = BASE / ".." / "data"

environment = Environment(
loader=FileSystemLoader(str(BASE))
)

out = DEST / "sponsors.html"
base_html = environment.get_template(str(Path("templates") / "base_sponsors.html"))

# Read data
dfs = []
sponsor_dir = DATA / "sponsors"
for datafile in sponsor_dir.glob("**/*.json"):
data = None
with open(datafile) as f:
try:
data = json.load(f)
except json.decoder.JSONDecodeError:
print("[ERROR] Failed to parse", datafile)
sys.exit(1)

df = pd.DataFrame(data["sponsors"])
df["level"] = df["level"].apply(lambda x : data["levels"][x] if x in data["levels"] else np.nan)
df["conference"] = datafile.parents[0].stem
df["year"] = data["year"]
dfs.append(df)

sponsors = pd.concat(dfs)
grouped = sponsors.drop(["conference", "year"], axis=1).groupby("name").sum().sort_values("level", ascending=False)

# End read data

context = {
"title": "Sponsors",
"description": "Historical data from European Conferences",
"sponsors": sponsors,
"grouped": grouped,
}

with open(out, mode="w", encoding="utf-8") as f:
f.write(base_html.render(context))
3 changes: 3 additions & 0 deletions scripts/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
requests
types-requests
jinja2
numpy
pandas
110 changes: 110 additions & 0 deletions scripts/templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<!doctype html>
<html class="">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
/* Class based dark mode */
@variant dark (&:where(.dark, .dark *));
</style>
<script>
if (localStorage.getItem('dark-mode') === 'true' || (!('dark-mode' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.querySelector('html').classList.add('dark');
} else {
document.querySelector('html').classList.remove('dark');
}
</script>
</head>
<body class="bg-white dark:bg-neutral-900">
<!-- navbar -->
<header class="sticky top-0 inset-x-0 flex flex-wrap md:justify-start md:flex-nowrap z-50 w-full text-sm">
<nav class="mt-4 relative max-w-2xl w-full bg-white border border-gray-200 rounded-[14px] mx-2 px-5 flex flex-wrap md:flex-nowrap items-center justify-between p-1 ps-4 md:py-0 sm:mx-auto dark:bg-neutral-900 dark:border-neutral-700">
<div class="text-black dark:text-white text-xl">
Conference<span class="text-yellow-600 dark:text-yellow-400">Stats</span>
</div>

<div id="hs-navbar-header-floating" class="hidden hs-collapse overflow-hidden transition-all duration-300 basis-full grow md:block" aria-labelledby="hs-navbar-header-floating-collapse">
<div class="flex flex-col md:flex-row md:items-center md:justify-end gap-2 md:gap-3 mt-3 md:mt-0 py-2 md:py-0 md:ps-7">
<a class="py-0.5 md:py-3 px-4 md:px-1 border-s-2 md:border-s-0 md:border-b-2 border-gray-800 font-medium text-gray-800 hover:text-gray-800 focus:outline-hidden dark:border-neutral-200 dark:text-neutral-200 dark:hover:text-neutral-200" href="#" aria-current="page">Sponsors</a>
<a class="py-0.5 md:py-3 px-4 md:px-1 border-s-2 md:border-s-0 md:border-b-2 border-transparent text-gray-500 hover:text-gray-800 focus:outline-hidden dark:text-neutral-400 dark:hover:text-neutral-200" href="#">Speakers</a>
</div>
</div>

<!-- Light/Dark toggler -->
<div class="flex flex-col justify-center ml-3">
<input type="checkbox" id="light-switch" class="light-switch sr-only" />
<label class="relative cursor-pointer p-2" for="light-switch">
<svg class="dark:hidden" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
<path class="fill-slate-300" d="M7 0h2v2H7zM12.88 1.637l1.414 1.415-1.415 1.413-1.413-1.414zM14 7h2v2h-2zM12.95 14.433l-1.414-1.413 1.413-1.415 1.415 1.414zM7 14h2v2H7zM2.98 14.364l-1.413-1.415 1.414-1.414 1.414 1.415zM0 7h2v2H0zM3.05 1.706 4.463 3.12 3.05 4.535 1.636 3.12z" />
<path class="fill-slate-400" d="M8 4C5.8 4 4 5.8 4 8s1.8 4 4 4 4-1.8 4-4-1.8-4-4-4Z" />
</svg>
<svg class="hidden dark:block" width="16" height="16" xmlns="http://www.w3.org/2000/svg">
<path class="fill-slate-400" d="M6.2 1C3.2 1.8 1 4.6 1 7.9 1 11.8 4.2 15 8.1 15c3.3 0 6-2.2 6.9-5.2C9.7 11.2 4.8 6.3 6.2 1Z" />
<path class="fill-slate-500" d="M12.5 5a.625.625 0 0 1-.625-.625 1.252 1.252 0 0 0-1.25-1.25.625.625 0 1 1 0-1.25 1.252 1.252 0 0 0 1.25-1.25.625.625 0 1 1 1.25 0c.001.69.56 1.249 1.25 1.25a.625.625 0 1 1 0 1.25c-.69.001-1.249.56-1.25 1.25A.625.625 0 0 1 12.5 5Z" />
</svg>
<span class="sr-only">Switch to light / dark version</span>
</label>
</div>

</nav>
</header>
<!-- end navbar -->

<div>
<div class="max-w-[85rem] mx-auto px-4 sm:px-6 lg:px-8 py-24 space-y-8">
<div class="max-w-3xl text-center mx-auto">
<h1 class="block font-medium text-black dark:text-gray-200 text-3xl sm:text-4xl md:text-5xl lg:text-6xl">
{{ title }}
</h1>
</div>

<div class="max-w-3xl text-center mx-auto">
<p class="text-lg text-black dark:text-white/70">{{ description }}</p>
</div>

<div class="max-w-3xl mx-auto text-black dark:text-white">
{% block content %}
{% endblock %}
</div>

</div>
</div>

</body>
<!-- Light/Dark mode -->
<script>
const lightSwitch = document.getElementById('light-switch');
if (localStorage.getItem('dark-mode') === 'true') {
lightSwitch.checked = true;
} else {
lightSwitch.checked = false;
}

lightSwitch.addEventListener('change', function() {
if (this.checked) {
document.documentElement.classList.add('dark');
localStorage.setItem('dark-mode', true);
} else {
document.documentElement.classList.remove('dark');
localStorage.setItem('dark-mode', false);
}
});
</script>
<!-- Table column sort -->
<script>
const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent;

const comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
)(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));

// do the work...
document.querySelectorAll('th').forEach(th => th.addEventListener('click', (() => {
const table = th.closest('table');
Array.from(table.querySelectorAll('tr:nth-child(n+2)'))
.sort(comparer(Array.from(th.parentNode.children).indexOf(th), this.asc = !this.asc))
.forEach(tr => table.appendChild(tr) );
})));
</script>
</html>
43 changes: 43 additions & 0 deletions scripts/templates/base_sponsors.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{% extends "templates/base.html" %}
{% block content %}
{% set sort_icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 inline"><path fill-rule="evenodd" d="M2.24 6.8a.75.75 0 0 0 1.06-.04l1.95-2.1v8.59a.75.75 0 0 0 1.5 0V4.66l1.95 2.1a.75.75 0 1 0 1.1-1.02l-3.25-3.5a.75.75 0 0 0-1.1 0L2.2 5.74a.75.75 0 0 0 .04 1.06Zm8 6.4a.75.75 0 0 0-.04 1.06l3.25 3.5a.75.75 0 0 0 1.1 0l3.25-3.5a.75.75 0 1 0-1.1-1.02l-1.95 2.1V6.75a.75.75 0 0 0-1.5 0v8.59l-1.95-2.1a.75.75 0 0 0-1.06-.04Z" clip-rule="evenodd" /></svg>' %}
<h2 class="block font-medium text-black dark:text-gray-200 text-xl sm:text-2xl md:text-3xl lg:text-4xl text-center py-5">
Ranking by amount
</h2>
<table class="border-collapse mx-auto">
<tr class="bg-gray-300 dark:bg-gray-800">
<th class="border border-gray-600 px-5 py-3">Company {{ sort_icon }}</th>
<th class="border border-gray-600 px-5 py-3">Total amount (EUR) {{ sort_icon }}</th>
</tr>
{% for idx, sponsor in grouped.iterrows() %}
{% if sponsor['name'] and ( sponsors['level'] is defined )%}
<tr>
<td class="border border-gray-600 p-3"><a href="{{ sponsor['website'] }}">{{ sponsor['name'] }}</a></td>
<td class="border border-gray-600 p-3 text-right">{{ sponsor['level'] }}</td>
</tr>
{% endif %}
{% endfor %}
</table>

<h2 class="block font-medium text-black dark:text-gray-200 text-xl sm:text-2xl md:text-3xl lg:text-4xl text-center py-5">
All data
</h2>
<table class="border-collapse mx-auto">
<tr class="bg-gray-300 dark:bg-gray-800">
<th class="border border-gray-600 px-5 py-3">Year {{ sort_icon }}</th>
<th class="border border-gray-600 px-5 py-3">Conference {{ sort_icon }}</th>
<th class="border border-gray-600 px-5 py-3">Company {{ sort_icon }}</th>
<th class="border border-gray-600 px-5 py-3">Amount (EUR) {{ sort_icon }}</th>
</tr>
{% for idx, sponsor in sponsors.iterrows() %}
{% if sponsor['name'] and ( sponsors['level'] is defined )%}
<tr>
<td class="border border-gray-600 p-3">{{ sponsor['year'] }}</td>
<td class="border border-gray-600 p-3">{{ sponsor['conference'] }}</td>
<td class="border border-gray-600 p-3"><a href="{{ sponsor['website'] }}">{{ sponsor['name'] }}</a></td>
<td class="border border-gray-600 p-3 text-right">{{ sponsor['level'] }}</td>
</tr>
{% endif %}
{% endfor %}
</table>
{% endblock %}