Skip to content

Commit 5a67feb

Browse files
committed
Timeline - Progress, Percentage & Time
1 parent 6003301 commit 5a67feb

9 files changed

Lines changed: 253 additions & 38 deletions

File tree

RELEASE.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.6.3 (2025-06-11)
2+
3+
- add human readable time to attack and defend events
4+
- add progress bar with points and percentage
5+
- add event type icons
6+
17
## 0.6.2 (2025-06-11)
28

39
- remove console.logs
@@ -8,6 +14,7 @@
814
- track api calls as events instead of page visits.
915
- initialize.env.mjs - check if all .env variables are set.
1016
- add proper favicons
17+
- fix layout
1118

1219
## 0.6.1 (2025-06-09)
1320

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "helldivers.bot",
3-
"version": "0.6.2",
3+
"version": "0.6.3",
44
"private": true,
55
"scripts": {
66
"dev": "next dev --turbopack",

public/icons/attack.webp

3.8 KB
Loading

public/icons/defend.webp

1.93 KB
Loading

src/components/h1/Galaxy/Galaxy.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ export default function Galaxy({ data, rebroadcast }) {
2323
<section
2424
id="galaxy"
2525
// flex w-full max-w-[800px] flex-grow flex-col justify-center
26-
className="flex flex-grow flex-col gap-4"
26+
className="flex flex-grow-[4] flex-col gap-4"
2727
>
2828
<div className="flex flex-row gap-2 text-3xl uppercase">
2929
<h1>Season {data.season}</h1>
3030
<span>|</span>
31-
<span>{elapsedTime.days} days</span>
31+
<span>Day {elapsedTime.days}</span>
3232
</div>
3333
<Map svgRef={svgRef} map={map} />
3434
<Tooltip svgRef={svgRef} data={data} map={map} />

src/components/h1/Galaxy/Tooltip.css

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
#tooltip {
22
background-color: rgba(0, 0, 0, 0.75);
33
border: 2px solid black;
4-
}
54

6-
progress {
7-
/* Remove default appearance */
8-
appearance: none;
9-
-webkit-appearance: none;
10-
width: 300px;
11-
height: 20px;
12-
border: none;
13-
background-color: #eee;
14-
border-radius: 0; /* Squared corners */
15-
overflow: hidden;
16-
}
5+
progress {
6+
/* Remove default appearance */
7+
appearance: none;
8+
-webkit-appearance: none;
9+
width: 300px;
10+
height: 20px;
11+
border: none;
12+
background-color: #eee;
13+
border-radius: 0; /* Squared corners */
14+
overflow: hidden;
15+
}
16+
progress::-webkit-progress-bar {
17+
background-color: #f70000;
18+
border-radius: 0;
19+
}
1720

18-
/* Webkit browsers (Chrome, Safari, Edge) */
19-
progress::-webkit-progress-bar {
20-
background-color: #f70000;
21-
border-radius: 0;
21+
progress::-webkit-progress-value {
22+
background-color: gold;
23+
border-radius: 0;
24+
}
2225
}
2326

24-
progress::-webkit-progress-value {
25-
background-color: gold;
26-
border-radius: 0;
27-
}
27+
/* Webkit browsers (Chrome, Safari, Edge) */
2828

2929
/* Firefox */
3030
progress::-moz-progress-bar {

src/components/h1/Timeline/Timeline.css

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,42 @@
22
background-color: rgba(0, 0, 0, 0.75);
33
border: 2px solid black;
44
}
5+
6+
#event.Defend {
7+
progress::-webkit-progress-bar {
8+
background-color: gold;
9+
border-radius: 0;
10+
}
11+
12+
progress::-webkit-progress-value {
13+
background-color: #f70000;
14+
border-radius: 0;
15+
}
16+
}
517
#event.Attack {
6-
border: 2px solid gold;
18+
progress::-webkit-progress-bar {
19+
background-color: #f70000;
20+
border-radius: 0;
21+
}
22+
23+
progress::-webkit-progress-value {
24+
background-color: gold;
25+
border-radius: 0;
26+
}
727
}
8-
#event.Defend {
9-
border: 2px solid red;
28+
29+
#event.success {
30+
/* border-left: 10px solid green; */
31+
background-color: rgba(40, 0, 0, 0.8);
32+
}
33+
#event.fail {
34+
/* border-left: 10px solid red; */
35+
background-color: rgba(0, 20, 0, 0.8);
1036
}
1137

1238
#event.active {
39+
/* border-left: 10px solid gold; */
40+
border: 2px solid gold;
1341
animation: pulse-red 6s infinite alternate;
1442
}
1543

@@ -22,13 +50,13 @@
2250
}
2351

2452
20% {
25-
background-color: rgb(255, 0, 0, 0.99);
53+
background-color: rgb(200, 0, 0, 0.99);
2654
}
2755
25% {
28-
background-color: rgba(255, 0, 0, 0.66);
56+
background-color: rgba(200, 0, 0, 0.66);
2957
}
3058
30% {
31-
background-color: rgb(255, 0, 0, 0.99);
59+
background-color: rgb(200, 0, 0, 0.99);
3260
}
3361

3462
/* 30% {

src/components/h1/Timeline/Timeline.jsx

Lines changed: 126 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// 'use client';
22
import './Timeline.css';
3+
import { timeAgo, timeUntil } from '@/utils/time';
34

45
export default function Timeline({ data }) {
56
const events = [...data.defend_events, ...data.attack_events];
@@ -35,21 +36,137 @@ function generateEvent(event) {
3536
} else {
3637
type = 'Attack';
3738
}
38-
const start = new Date(event.start_time * 1000).toLocaleString('en-GB');
39-
const end = new Date(event.end_time * 1000).toLocaleString('en-GB');
39+
40+
const start = timeAgo(event.start_time * 1000);
41+
const end = timeAgo(event.end_time * 1000);
42+
// const start = new Date(event.start_time * 1000).toLocaleString('en-GB');
43+
// const end = new Date(event.end_time * 1000).toLocaleString('en-GB');
44+
45+
const percent = (event.points / event.points_max) * 100;
46+
const progress = util_evaluate_progress(event);
47+
// console.log(event, progress);
4048

4149
return (
4250
<article
4351
id="event"
4452
key={event.event_id}
45-
className={`flex flex-col gap-2 rounded-sm p-2 ${type} ${event?.status} ${event?.status === 'active' ? 'active' : ''}`}
53+
className={`relative flex flex-col gap-2 overflow-hidden rounded-sm p-2 ${type} ${event?.status} ${event?.status === 'active' ? 'active' : ''}`}
4654
>
47-
<h3>{type} Event</h3>
48-
Started at {start}
49-
<br />
50-
Finished at {end}
51-
<br />
52-
{event.status}
55+
<div className="flex gap-2">
56+
<img
57+
src={`/icons/faction${event?.enemy}.webp`}
58+
alt="Logo of Helldivers Bot, which is a cartoon depiction of a spy sattelite"
59+
width={20}
60+
height={20}
61+
/>
62+
<h3>{type} Event</h3>
63+
</div>
64+
<div className="z-20 flex flex-col gap-2 text-sm">
65+
<div className="flex justify-between gap-2">
66+
<span>Started {start}</span>
67+
{end.includes('ago') ?
68+
<span>Finished {end}</span>
69+
: <span>Finishes in {end}</span>}
70+
</div>
71+
72+
<div>{progress}</div>
73+
74+
<div className="relative">
75+
<progress value={percent} max="100" className="h-5 w-full"></progress>
76+
<span className="absolute left-1 text-black">
77+
{event.points} / {event.points_max}
78+
</span>
79+
<span className="absolute right-1 text-black">
80+
{percent.toFixed(2)}%
81+
</span>
82+
</div>
83+
</div>
84+
85+
<img
86+
src={`/icons/${type}.webp`}
87+
alt={`${type} Event Icon`}
88+
className="absolute -bottom-5 right-0 z-0 h-[80%] opacity-65"
89+
/>
5390
</article>
5491
);
5592
}
93+
94+
function util_evaluate_progress(event) {
95+
// Get the current time as a timestamp
96+
const currentTime = Math.floor(Date.now() / 1000);
97+
98+
// Calculate total time in milliseconds
99+
const totalTime = event.end_time - event.start_time;
100+
// console.log('totalTime', totalTime);
101+
102+
// Calculate elapsed time in milliseconds
103+
const elapsedTime = currentTime - event.start_time;
104+
// console.log('elapsedTime', elapsedTime);
105+
106+
// Calculate remaining time in milliseconds
107+
const remainingTime = event.end_time - currentTime;
108+
// console.log('remainingTime', remainingTime);
109+
110+
// Calculate the expected rate of progress (points per millisecond)
111+
const expectedRate = event.points_max / totalTime;
112+
113+
// Calculate the current rate of progress (points per millisecond)
114+
const currentRate = event.points / elapsedTime;
115+
116+
// Calculate the expected points by now
117+
const expectedPoints = expectedRate * elapsedTime;
118+
119+
// Calculate the remaining points
120+
const remainingPoints = event.points_max - event.points;
121+
122+
// Calculate the required rate for the remaining time (points per millisecond)
123+
const requiredRate = remainingPoints / remainingTime;
124+
125+
// 10% buffer
126+
const buffer = expectedPoints * 0.1;
127+
// Determine the progress status
128+
let status;
129+
if (event.points > expectedPoints + buffer) {
130+
status = 'Ahead';
131+
} else if (event.points < expectedPoints) {
132+
status = 'Behind';
133+
} else {
134+
status = 'On track';
135+
}
136+
137+
// Determine if the current rate is sufficient
138+
// let rateStatus;
139+
// if (currentRate >= requiredRate) {
140+
// rateStatus = 'on track to meet your goal';
141+
// } else {
142+
// rateStatus = 'need to increase your rate to meet your goal';
143+
// }
144+
145+
let pointDifference = Math.abs(expectedPoints - event.points);
146+
147+
const progress = {
148+
expectedRate: expectedRate.toFixed(6), // Adjust precision as needed
149+
currentRate: currentRate.toFixed(6),
150+
expectedPoints: expectedPoints.toFixed(0),
151+
remainingPoints: remainingPoints.toFixed(0),
152+
requiredRate: requiredRate.toFixed(6),
153+
status: status,
154+
// rateStatus: rateStatus,
155+
};
156+
157+
if (event.status === 'active') {
158+
return `${status} by ${pointDifference.toFixed(0)} points`;
159+
}
160+
// if (event.status === 'success') {
161+
// const remaining_minutes = 120 - Math.floor(elapsedTime / 60);
162+
// const win_text =
163+
// remaining_minutes > 1 ?
164+
// `${remaining_minutes} minutes`
165+
// : `${remaining_minutes} minute`;
166+
167+
// return `Won with ${win_text} to spare.`;
168+
// }
169+
// if (event.status === 'failure') {
170+
// return `Lost ${pointDifference.toFixed(0)} points`;
171+
// }
172+
}

src/utils/time.mjs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,67 @@ export function elapsedSeasonTime(season_duration) {
8181
};
8282
return time;
8383
}
84+
8485
//endregion
86+
87+
//human time
88+
89+
export function timeAgo(from, to = new Date()) {
90+
// Convert to Date objects if needed
91+
from = new Date(from);
92+
to = new Date(to);
93+
94+
const sign = Math.floor((to - from) / 1000);
95+
const seconds = Math.abs(sign);
96+
97+
if (seconds < 60) {
98+
return `${seconds} second${seconds !== 1 ? 's' : ''} ${sign > 0 ? 'ago' : ''}`;
99+
}
100+
const minutes = Math.floor(seconds / 60);
101+
if (minutes < 60) {
102+
return `${minutes} minute${minutes !== 1 ? 's' : ''} ${sign > 0 ? 'ago' : ''}`;
103+
}
104+
const hours = Math.floor(minutes / 60);
105+
if (hours < 24) {
106+
return `${hours} hour${hours !== 1 ? 's' : ''} ${sign > 0 ? 'ago' : ''}`;
107+
}
108+
const days = Math.floor(hours / 24);
109+
if (days < 30) {
110+
return `${days} day${days !== 1 ? 's' : ''} ${sign > 0 ? 'ago' : ''}`;
111+
}
112+
const months = Math.floor(days / 30);
113+
if (months < 12) {
114+
return `${months} month${months !== 1 ? 's' : ''} ${sign > 0 ? 'ago' : ''}`;
115+
}
116+
const years = Math.floor(days / 365);
117+
return `${years} year${years !== 1 ? 's' : ''} ${sign > 0 ? 'ago' : ''}`;
118+
}
119+
120+
// export function timeUntil(from, to = new Date()) {
121+
// from = new Date(from);
122+
// to = new Date(to);
123+
124+
// const seconds = Math.floor((to - from) / 1000);
125+
126+
// if (seconds < 60) {
127+
// return `${seconds} second${seconds !== 1 ? 's' : ''} `;
128+
// }
129+
// const minutes = Math.floor(seconds / 60);
130+
// if (minutes < 60) {
131+
// return `${minutes} minute${minutes !== 1 ? 's' : ''} `;
132+
// }
133+
// const hours = Math.floor(minutes / 60);
134+
// if (hours < 24) {
135+
// return `${hours} hour${hours !== 1 ? 's' : ''} `;
136+
// }
137+
// const days = Math.floor(hours / 24);
138+
// if (days < 30) {
139+
// return `${days} day${days !== 1 ? 's' : ''} `;
140+
// }
141+
// const months = Math.floor(days / 30);
142+
// if (months < 12) {
143+
// return `${months} month${months !== 1 ? 's' : ''} `;
144+
// }
145+
// const years = Math.floor(days / 365);
146+
// return `${years} year${years !== 1 ? 's' : ''} `;
147+
// }

0 commit comments

Comments
 (0)