Skip to content

Commit 811408b

Browse files
Update documentation
0 parents  commit 811408b

File tree

10 files changed

+673
-0
lines changed

10 files changed

+673
-0
lines changed

.nojekyll

Whitespace-only changes.

CNAME

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
stats.dangerzone.rocks

app.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { h, render } from 'https://esm.sh/preact';
2+
import { useState } from 'https://esm.sh/preact/hooks';
3+
import { App } from './components/App.js';
4+
5+
const stats = window.INITIAL_STATS;
6+
const generatedAt = window.GENERATED_AT;
7+
8+
render(h(App, { stats, generatedAt }), document.getElementById('app'));

components/App.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { h } from 'https://esm.sh/preact';
2+
import { useState } from 'https://esm.sh/preact/hooks';
3+
import { Stats } from './Stats.js';
4+
import { Chart } from './Chart.js';
5+
import { YearlyChart } from './YearlyChart.js';
6+
7+
export function App({ stats, generatedAt }) {
8+
const [activeTab, setActiveTab] = useState('charts');
9+
10+
return h('div', { class: 'container' }, [
11+
h('header', { class: 'header' }, [
12+
h('h1', null, 'Dangerzone Release Stats'),
13+
h('p', null, `Generated at: ${new Date(generatedAt).toLocaleString()}`)
14+
]),
15+
h('nav', { class: 'tabs' }, [
16+
h('button', {
17+
class: activeTab === 'charts' ? 'active' : '',
18+
onClick: () => setActiveTab('charts')
19+
}, 'By Release'),
20+
h('button', {
21+
class: activeTab === 'yearly' ? 'active' : '',
22+
onClick: () => setActiveTab('yearly')
23+
}, 'By Year'),
24+
h('button', {
25+
class: activeTab === 'overview' ? 'active' : '',
26+
onClick: () => setActiveTab('overview')
27+
}, 'Overview')
28+
]),
29+
activeTab === 'charts' ? h(Chart, { stats }) :
30+
activeTab === 'yearly' ? h(YearlyChart, { stats }) :
31+
h(Stats, { stats })
32+
]);
33+
}

components/Chart.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { h } from 'https://esm.sh/preact';
2+
3+
function getPlatform(assetName) {
4+
assetName = assetName.toLowerCase();
5+
if (assetName.includes('msi')) return 'Windows';
6+
if (assetName.includes('arm64.dmg')) return 'Mac Silicon';
7+
if (assetName.includes('i686.dmg') || assetName.includes('.dmg')) return 'Mac Intel';
8+
if (assetName.includes('container')) return 'Container';
9+
return 'Other';
10+
}
11+
12+
function getColorForPlatform(platform) {
13+
switch (platform) {
14+
case 'Windows': return '#00A4EF';
15+
case 'Mac Intel': return '#A2AAAD';
16+
case 'Mac Silicon': return '#C4C4C4';
17+
case 'Container': return '#FFD700';
18+
default: return '#FF69B4';
19+
}
20+
}
21+
22+
function formatDate(dateString) {
23+
return new Date(dateString).toLocaleDateString(undefined, {
24+
year: 'numeric',
25+
month: 'short'
26+
});
27+
}
28+
29+
export function Chart({ stats }) {
30+
console.log('Received stats:', stats);
31+
32+
if (!stats || !stats.releases || !Array.isArray(stats.releases)) {
33+
return h('div', { class: 'charts' }, 'No data available');
34+
}
35+
36+
const platforms = ['Windows', 'Mac Intel', 'Mac Silicon', 'Container', 'Other'];
37+
38+
const downloadsByRelease = stats.releases
39+
.filter(release => release && release.name && release.published_at)
40+
.map(release => {
41+
console.log('Processing release:', release);
42+
return {
43+
name: (release.name || '').replace(/^Dangerzone /, ''),
44+
date: release.published_at,
45+
platforms: platforms.reduce((acc, platform) => {
46+
acc[platform] = 0;
47+
return acc;
48+
}, {}),
49+
total: 0,
50+
assets: release.assets || []
51+
};
52+
})
53+
.map(release => {
54+
release.assets.forEach(asset => {
55+
const platform = getPlatform(asset.name);
56+
release.platforms[platform] += asset.download_count || 0;
57+
});
58+
59+
release.total = Object.values(release.platforms).reduce((a, b) => a + b, 0);
60+
return release;
61+
})
62+
.sort((a, b) => new Date(b.date) - new Date(a.date));
63+
64+
console.log('Processed releases:', downloadsByRelease);
65+
66+
if (downloadsByRelease.length === 0) {
67+
return h('div', { class: 'charts' }, 'No release data available');
68+
}
69+
70+
const maxDownloads = Math.max(...downloadsByRelease.map(r => r.total));
71+
72+
return h('div', { class: 'charts' }, [
73+
h('div', { class: 'chart horizontal' }, [
74+
h('div', { class: 'chart-legend' },
75+
platforms.map(platform =>
76+
h('div', { class: 'legend-item' }, [
77+
h('span', {
78+
class: 'legend-color',
79+
style: `background-color: ${getColorForPlatform(platform)}`
80+
}),
81+
h('span', { class: 'legend-label' }, platform)
82+
])
83+
)
84+
),
85+
h('div', { class: 'chart-container horizontal' },
86+
downloadsByRelease.map(release =>
87+
h('div', { class: 'bar-group horizontal' }, [
88+
h('div', { class: 'bar-label horizontal' }, [
89+
h('span', { class: 'version-label' }, release.name),
90+
h('span', { class: 'date-label' }, formatDate(release.date))
91+
]),
92+
h('div', { class: 'stacked-bars horizontal' },
93+
platforms.map(platform => {
94+
const width = (release.platforms[platform] / maxDownloads) * 400;
95+
return h('div', {
96+
class: 'bar stacked-bar horizontal',
97+
style: `
98+
width: ${width}px;
99+
background-color: ${getColorForPlatform(platform)};
100+
`,
101+
title: `${platform}: ${release.platforms[platform].toLocaleString()} downloads`
102+
});
103+
})
104+
),
105+
h('div', { class: 'total-downloads' },
106+
`${release.total.toLocaleString()} downloads`
107+
)
108+
])
109+
)
110+
)
111+
])
112+
]);
113+
}

components/Stats.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { h } from 'https://esm.sh/preact';
2+
3+
export function Stats({ stats }) {
4+
return h('div', { class: 'stats-grid' }, [
5+
...stats.releases.map(release =>
6+
h('div', { class: 'stat-card' }, [
7+
h('h2', null, [
8+
h('a', { href: release.html_url, target: '_blank' }, release.name)
9+
]),
10+
h('p', { class: 'release-date' },
11+
new Date(release.published_at).toLocaleDateString()
12+
),
13+
h('div', { class: 'assets-list' },
14+
release.assets.map(asset =>
15+
h('div', { class: 'asset-item' }, [
16+
h('a', {
17+
href: asset.download_url,
18+
class: 'asset-name',
19+
target: '_blank'
20+
}, asset.name),
21+
h('span', { class: 'download-count' },
22+
`${asset.download_count.toLocaleString()} downloads`
23+
)
24+
])
25+
)
26+
)
27+
])
28+
)
29+
]);
30+
}

components/YearlyChart.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { h } from 'https://esm.sh/preact';
2+
3+
function getPlatform(assetName) {
4+
assetName = assetName.toLowerCase();
5+
if (assetName.includes('msi')) return 'Windows';
6+
if (assetName.includes('arm64.dmg')) return 'Mac Silicon';
7+
if (assetName.includes('i686.dmg') || assetName.includes('.dmg')) return 'Mac Intel';
8+
if (assetName.includes('container')) return 'Container';
9+
return 'Other';
10+
}
11+
12+
function getColorForPlatform(platform) {
13+
switch (platform) {
14+
case 'Windows': return '#00A4EF';
15+
case 'Mac Intel': return '#A2AAAD';
16+
case 'Mac Silicon': return '#C4C4C4';
17+
case 'Container': return '#FFD700';
18+
default: return '#FF69B4';
19+
}
20+
}
21+
22+
export function YearlyChart({ stats }) {
23+
if (!stats || !stats.releases || !Array.isArray(stats.releases)) {
24+
return h('div', { class: 'charts' }, 'No data available');
25+
}
26+
27+
const platforms = ['Windows', 'Mac Intel', 'Mac Silicon', 'Container', 'Other'];
28+
29+
// Group downloads by year
30+
const yearlyDownloads = stats.releases.reduce((acc, release) => {
31+
const year = new Date(release.published_at).getFullYear();
32+
33+
if (!acc[year]) {
34+
acc[year] = platforms.reduce((platformAcc, platform) => {
35+
platformAcc[platform] = 0;
36+
return platformAcc;
37+
}, { total: 0 });
38+
}
39+
40+
release.assets.forEach(asset => {
41+
const platform = getPlatform(asset.name);
42+
acc[year][platform] += asset.download_count || 0;
43+
acc[year].total += asset.download_count || 0;
44+
});
45+
46+
return acc;
47+
}, {});
48+
49+
// Convert to array and sort by year (descending)
50+
const yearlyStats = Object.entries(yearlyDownloads)
51+
.map(([year, stats]) => ({
52+
year: parseInt(year),
53+
platforms: stats,
54+
total: stats.total
55+
}))
56+
.sort((a, b) => b.year - a.year);
57+
58+
const maxDownloads = Math.max(...yearlyStats.map(y => y.total));
59+
60+
return h('div', { class: 'charts' }, [
61+
h('div', { class: 'chart horizontal' }, [
62+
h('div', { class: 'chart-legend' },
63+
platforms.map(platform =>
64+
h('div', { class: 'legend-item' }, [
65+
h('span', {
66+
class: 'legend-color',
67+
style: `background-color: ${getColorForPlatform(platform)}`
68+
}),
69+
h('span', { class: 'legend-label' }, platform)
70+
])
71+
)
72+
),
73+
h('div', { class: 'chart-container horizontal' },
74+
yearlyStats.map(yearStat =>
75+
h('div', { class: 'bar-group horizontal' }, [
76+
h('div', { class: 'bar-label horizontal' }, [
77+
h('span', { class: 'version-label' }, yearStat.year)
78+
]),
79+
h('div', { class: 'stacked-bars horizontal' },
80+
platforms.map(platform => {
81+
const width = (yearStat.platforms[platform] / maxDownloads) * 400;
82+
return h('div', {
83+
class: 'bar stacked-bar horizontal',
84+
style: `
85+
width: ${width}px;
86+
background-color: ${getColorForPlatform(platform)};
87+
`,
88+
title: `${platform}: ${yearStat.platforms[platform].toLocaleString()} downloads`
89+
});
90+
})
91+
),
92+
h('div', { class: 'total-downloads' },
93+
`${yearStat.total.toLocaleString()} downloads`
94+
)
95+
])
96+
)
97+
)
98+
])
99+
]);
100+
}

index.html

Lines changed: 20 additions & 0 deletions
Large diffs are not rendered by default.

stats.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)