From ed80ed838fad0cbea3b7b3676b6b68b188177892 Mon Sep 17 00:00:00 2001 From: xangelo Date: Mon, 6 Jun 2022 23:11:24 -0400 Subject: [PATCH] show unread mail count in topbar 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. --- public/game.html | 2 +- public/scifi.css | 13 +++++++++++++ src/api.ts | 37 ++++++++++++++++++++++++------------- src/render/mail.ts | 8 +++++--- src/render/topbar.ts | 13 ++++++++++++- src/repository/mail.ts | 15 ++++++++++++++- 6 files changed, 69 insertions(+), 19 deletions(-) diff --git a/public/game.html b/public/game.html index 2ac4998..1115b82 100644 --- a/public/game.html +++ b/public/game.html @@ -38,7 +38,7 @@ Map
  • - Mail + Mail
  • diff --git a/public/scifi.css b/public/scifi.css index 98c99bd..ce21ee1 100644 --- a/public/scifi.css +++ b/public/scifi.css @@ -191,6 +191,7 @@ footer { } #nav li a { padding: 10px 25px; + position: relative; } #nav li a:hover { background-color: var(--border); @@ -416,6 +417,18 @@ form > div { margin-bottom: 5px; } +.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; } diff --git a/src/api.ts b/src/api.ts index 6638194..5b1edc6 100644 --- a/src/api.ts +++ b/src/api.ts @@ -100,6 +100,8 @@ server.get<{params: { cityId: string }}, string>('/city/:cityId', async req => { 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), @@ -111,13 +113,14 @@ server.get<{}, string>('/poll/overview', async req => { 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(); + const unreadMail = await mailRepo.countUnread(account.id); const buildQueues = await cityRepo.getBuildQueues(account.id); const usage = { @@ -126,12 +129,13 @@ server.get<{}, string>('/poll/construction', async req => { 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); + const unreadMail = await mailRepo.countUnread(account.id); const unitTrainingQueues = await cityRepo.getUnitTrainingQueues(account.id); const units = await cityRepo.unitRepository.list(); @@ -145,12 +149,13 @@ server.get<{}, string>('/poll/unit-training', async req => { return renderUnitTraining(city, units, unitTrainingQueues) + topbar({ ...city, ...usage - }); + }, unreadMail); }); 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) { @@ -172,12 +177,13 @@ server.post<{body: {sector: string}}, string>('/poll/map', async req => { return renderOverworldMap(await cityRepo.findAllInSector(sector), city, sector) + topbar({ ...city, ...usage - }); + }, unreadMail); }); 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), @@ -189,7 +195,7 @@ server.get<{}, string>('/poll/mailroom', async req => { return renderMailroom(await mailRepo.listReceivedMessages(account.id)) + topbar({ ...city, ...usage - }); + }, unreadMail); }); @@ -336,24 +342,29 @@ server.post<{ }); }, 'reload-outgoing-attacks'); -server.get('/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); + 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 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); + const unreadMail = await mailRepo.countUnread(acct.id); - return renderMessage(msg); + return renderMailroom(await mailRepo.listReceivedMessages(acct.id), msg) + topbar({ + ...city, + ...usage + }, unreadMail); }); server.get('/attacks/outgoing', async req => { diff --git a/src/render/mail.ts b/src/render/mail.ts index a0b3b75..0048b59 100644 --- a/src/render/mail.ts +++ b/src/render/mail.ts @@ -1,15 +1,16 @@ import { DateTime } from "luxon"; import { MessageWithNames } from "../repository/mail"; -export function renderMailroom(mail: MessageWithNames[]): string { +export function renderMailroom(mail: MessageWithNames[], msg?: MessageWithNames): string { return ` -
    +

    Mail

    + ${mail.map(msg => { return ` @@ -21,11 +22,12 @@ export function renderMailroom(mail: MessageWithNames[]): string { + `; }).join("\n")}
    From Subject Sent AtRead At
    ${DateTime.fromMillis(msg.sent_at)}${msg.read_at === 0 ? 'Unread' : DateTime.fromMillis(msg.read_at)}
    -
    +
    ${msg ? renderMessage(msg) : ''}
    `; } diff --git a/src/render/topbar.ts b/src/render/topbar.ts index 94313c5..a6dc789 100644 --- a/src/render/topbar.ts +++ b/src/render/topbar.ts @@ -7,11 +7,22 @@ type Usage = { energyProductionPerTick: number; } +function renderUnreadBadge(count: number): string { + return `${count}`; -export function topbar(city: City & Usage): string { +} + +function mailLink(unreadCount: number): string { + return ` + Mail ${unreadCount > 0 ? renderUnreadBadge(unreadCount) : ''} + `; +} + +export function topbar(city: City & Usage, unreadMail: number): string { const foodRateOfChange = city.foodProductionPerTick - city.foodUsagePerTick; const energyRateOfChange = city.energyProductionPerTick - city.energyUsagePerTick; const oob = ` + ${mailLink(unreadMail)}
    diff --git a/src/repository/mail.ts b/src/repository/mail.ts index bf480c0..0fe530b 100644 --- a/src/repository/mail.ts +++ b/src/repository/mail.ts @@ -56,6 +56,19 @@ export class MailRepository extends Repository { return res.pop(); } + async countUnread(to: string): Promise { + 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 { return this.db.raw(`select m.*, a.username from mail m @@ -63,4 +76,4 @@ export class MailRepository extends Repository { where m.to_account = ? order by sent_at desc`, to); } -} \ No newline at end of file +} -- 2.25.1