The system tracks your unread mail and displays it to you on the menu
bar. Reading your unread mail will decrease this count to 0. At 0, the
badge next to the mail link disappears.
<a href="#" hx-target="#main" hx-post="/poll/map" hx-trigger="click">Map</a>
</li>
<li>
<a href="#" hx-target="#main" hx-post="/poll/map" hx-trigger="click">Map</a>
</li>
<li>
- <a href="#" hx-target="#main" hx-get="/poll/mailroom" hx-trigger="click">Mail</a>
+ <a href="#" hx-target="#main" hx-get="/poll/mailroom" hx-trigger="click" id="mail-link">Mail</a>
}
#nav li a {
padding: 10px 25px;
}
#nav li a {
padding: 10px 25px;
}
#nav li a:hover {
background-color: var(--border);
}
#nav li a:hover {
background-color: var(--border);
+.badge {
+ border-radius: 5px;
+ padding: 0 6px;
+ font-size: 0.7rem;
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+.badge.danger {
+ background-color: var(--red-border);
+}
+
#stats {
margin-top: 1rem;
}
#stats {
margin-top: 1rem;
}
server.get<{}, string>('/poll/overview', async req => {
const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
const city = await cityRepo.getUsersCity(account.id);
server.get<{}, string>('/poll/overview', async req => {
const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
const city = await cityRepo.getUsersCity(account.id);
+ const unreadMail = await mailRepo.countUnread(account.id);
+
const usage = {
foodUsagePerTick: await cityRepo.foodUsagePerTick(city),
const usage = {
foodUsagePerTick: await cityRepo.foodUsagePerTick(city),
return renderKingomOverview({
...city,
...usage
return renderKingomOverview({
...city,
...usage
- }, account) + topbar({...city, ...usage});
+ }, account) + topbar({...city, ...usage}, unreadMail);
});
server.get<{}, string>('/poll/construction', async req => {
const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
const city = await cityRepo.getUsersCity(account.id);
const buildings = await cityRepo.buildingRepository.list();
});
server.get<{}, string>('/poll/construction', async req => {
const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
const city = await cityRepo.getUsersCity(account.id);
const buildings = await cityRepo.buildingRepository.list();
+ const unreadMail = await mailRepo.countUnread(account.id);
const buildQueues = await cityRepo.getBuildQueues(account.id);
const usage = {
const buildQueues = await cityRepo.getBuildQueues(account.id);
const usage = {
energyUsagePerTick: await cityRepo.energyUsagePerTick(city),
energyProductionPerTick: await cityRepo.energyProductionPerTick(city)
}
energyUsagePerTick: await cityRepo.energyUsagePerTick(city),
energyProductionPerTick: await cityRepo.energyProductionPerTick(city)
}
- return renderLandDevelopment(city, buildings, buildQueues) + topbar({...city, ...usage});
+ return renderLandDevelopment(city, buildings, buildQueues) + topbar({...city, ...usage}, unreadMail);
});
server.get<{}, string>('/poll/unit-training', async req => {
const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
const city = await cityRepo.getUsersCity(account.id);
});
server.get<{}, string>('/poll/unit-training', async req => {
const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
const city = await cityRepo.getUsersCity(account.id);
+ const unreadMail = await mailRepo.countUnread(account.id);
const unitTrainingQueues = await cityRepo.getUnitTrainingQueues(account.id);
const units = await cityRepo.unitRepository.list();
const unitTrainingQueues = await cityRepo.getUnitTrainingQueues(account.id);
const units = await cityRepo.unitRepository.list();
return renderUnitTraining(city, units, unitTrainingQueues) + topbar({
...city,
...usage
return renderUnitTraining(city, units, unitTrainingQueues) + topbar({
...city,
...usage
});
server.post<{body: {sector: string}}, string>('/poll/map', async req => {
const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
const city = await cityRepo.getUsersCity(account.id);
});
server.post<{body: {sector: string}}, string>('/poll/map', async req => {
const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
const city = await cityRepo.getUsersCity(account.id);
+ const unreadMail = await mailRepo.countUnread(account.id);
let sector = city.sector_id;
if(req.body.sector) {
let sector = city.sector_id;
if(req.body.sector) {
return renderOverworldMap(await cityRepo.findAllInSector(sector), city, sector) + topbar({
...city,
...usage
return renderOverworldMap(await cityRepo.findAllInSector(sector), city, sector) + topbar({
...city,
...usage
});
server.get<{}, string>('/poll/mailroom', async req => {
const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
const city = await cityRepo.getUsersCity(account.id);
});
server.get<{}, string>('/poll/mailroom', async req => {
const account = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
const city = await cityRepo.getUsersCity(account.id);
+ const unreadMail = await mailRepo.countUnread(account.id);
const usage = {
foodUsagePerTick: await cityRepo.foodUsagePerTick(city),
const usage = {
foodUsagePerTick: await cityRepo.foodUsagePerTick(city),
return renderMailroom(await mailRepo.listReceivedMessages(account.id)) + topbar({
...city,
...usage
return renderMailroom(await mailRepo.listReceivedMessages(account.id)) + topbar({
...city,
...usage
});
}, 'reload-outgoing-attacks');
});
}, 'reload-outgoing-attacks');
-server.get<void, string>('/messages', async req => {
- const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
- const msgs = await mailRepo.listReceivedMessages(acct.id);
-
- return JSON.stringify(msgs);
-});
-
server.get<{params: {id: string}}, string>('/messages/:id', async req => {
const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
server.get<{params: {id: string}}, string>('/messages/:id', async req => {
const acct = await accountRepo.validate(req.authInfo.accountId, req.authInfo.token);
+ const city = await cityRepo.getUsersCity(acct.id);
const msg = await mailRepo.getMessage(req.params.id, acct.id);
if(!msg) {
throw new NotFoundError('No such message', ERROR_CODE.DUPLICATE_CACHE_KEY);
}
const msg = await mailRepo.getMessage(req.params.id, acct.id);
if(!msg) {
throw new NotFoundError('No such message', ERROR_CODE.DUPLICATE_CACHE_KEY);
}
+ const usage = {
+ foodUsagePerTick: await cityRepo.foodUsagePerTick(city),
+ foodProductionPerTick: await cityRepo.foodProductionPerTick(city),
+ energyUsagePerTick: await cityRepo.energyUsagePerTick(city),
+ energyProductionPerTick: await cityRepo.energyProductionPerTick(city)
+ }
+
await mailRepo.markAsRead(msg.id, msg.to_account);
await mailRepo.markAsRead(msg.id, msg.to_account);
+ const unreadMail = await mailRepo.countUnread(acct.id);
- return renderMessage(msg);
+ return renderMailroom(await mailRepo.listReceivedMessages(acct.id), msg) + topbar({
+ ...city,
+ ...usage
+ }, unreadMail);
});
server.get<void, string>('/attacks/outgoing', async req => {
});
server.get<void, string>('/attacks/outgoing', async req => {
import { DateTime } from "luxon";
import { MessageWithNames } from "../repository/mail";
import { DateTime } from "luxon";
import { MessageWithNames } from "../repository/mail";
-export function renderMailroom(mail: MessageWithNames[]): string {
+export function renderMailroom(mail: MessageWithNames[], msg?: MessageWithNames): string {
- <div hx-trigger="every 600s" hx-get="/poll/mailroom">
+ <div hx-trigger="every 600s" hx-get="/poll/mailroom" hx-swap-oob="true" id="main">
<h2 data-augmented-ui="tl-clip bl-clip none">Mail</h2>
<table>
<tr>
<th>From</th>
<th>Subject</th>
<th>Sent At</th>
<h2 data-augmented-ui="tl-clip bl-clip none">Mail</h2>
<table>
<tr>
<th>From</th>
<th>Subject</th>
<th>Sent At</th>
</tr>
${mail.map(msg => {
return `
</tr>
${mail.map(msg => {
return `
</a>
</td>
<td>${DateTime.fromMillis(msg.sent_at)}</td>
</a>
</td>
<td>${DateTime.fromMillis(msg.sent_at)}</td>
+ <td>${msg.read_at === 0 ? 'Unread' : DateTime.fromMillis(msg.read_at)}</td>
</tr>
`;
}).join("\n")}
</table>
</tr>
`;
}).join("\n")}
</table>
- <div id="individual-message"></div>
+ <div id="individual-message">${msg ? renderMessage(msg) : ''}</div>
energyProductionPerTick: number;
}
energyProductionPerTick: number;
}
+function renderUnreadBadge(count: number): string {
+ return `<span class="badge danger">${count}</span>`;
-export function topbar(city: City & Usage): string {
+}
+
+function mailLink(unreadCount: number): string {
+ return `
+ <a href="#" hx-target="#main" hx-get="/poll/mailroom" hx-trigger="click" id="mail-link" hx-swap-oob="true">Mail ${unreadCount > 0 ? renderUnreadBadge(unreadCount) : ''}</a>
+ `;
+}
+
+export function topbar(city: City & Usage, unreadMail: number): string {
const foodRateOfChange = city.foodProductionPerTick - city.foodUsagePerTick;
const energyRateOfChange = city.energyProductionPerTick - city.energyUsagePerTick;
const oob = `
const foodRateOfChange = city.foodProductionPerTick - city.foodUsagePerTick;
const energyRateOfChange = city.energyProductionPerTick - city.energyUsagePerTick;
const oob = `
+ ${mailLink(unreadMail)}
<div class="col" id="info-bar" hx-swap-oob="true">
<span>
<b data-augmented-ui="all-hex border" title="Credits">₡</b>
<div class="col" id="info-bar" hx-swap-oob="true">
<span>
<b data-augmented-ui="all-hex border" title="Credits">₡</b>
+ async countUnread(to: string): Promise<number> {
+ const res = await this.db.raw<{unread: number}>(`select count(id) as
+ unread from mail
+ where to_account = ? and read_at = 0`, to);
+
+ try {
+ return parseInt(res[0].unread.toString()) || 0;
+ }
+ catch(e) {
+ return 0;
+ }
+ }
+
async listReceivedMessages(to: string): Promise<MessageWithNames[]> {
return this.db.raw<MessageWithNames[]>(`select m.*, a.username
from mail m
async listReceivedMessages(to: string): Promise<MessageWithNames[]> {
return this.db.raw<MessageWithNames[]>(`select m.*, a.username
from mail m
where m.to_account = ?
order by sent_at desc`, to);
}
where m.to_account = ?
order by sent_at desc`, to);
}
-}
\ No newline at end of file