impl uploaded at and updated at stats

This commit is contained in:
HJfod 2024-03-03 00:28:06 +02:00
parent abe41a79a3
commit 38852566a6
3 changed files with 70 additions and 6 deletions

View file

@ -1,6 +1,7 @@
#include "Server.hpp" #include "Server.hpp"
#include <Geode/utils/JsonValidation.hpp> #include <Geode/utils/JsonValidation.hpp>
#include <loader/ModMetadataImpl.hpp> #include <loader/ModMetadataImpl.hpp>
#include <fmt/chrono.h>
using namespace server; using namespace server;
@ -75,6 +76,41 @@ const char* server::sortToString(ModsSort sorting) {
} }
} }
std::string ServerDateTime::toAgoString() const {
auto const fmtPlural = [](auto count, auto unit) {
if (count == 1) {
return fmt::format("{} {} ago", count, unit);
}
return fmt::format("{} {}s ago", count, unit);
};
auto now = Clock::now();
auto len = std::chrono::duration_cast<std::chrono::minutes>(now - value).count();
if (len < 60) {
return fmtPlural(len, "minute");
}
len = std::chrono::duration_cast<std::chrono::hours>(now - value).count();
if (len < 24) {
return fmtPlural(len, "hour");
}
len = std::chrono::duration_cast<std::chrono::days>(now - value).count();
if (len < 31) {
return fmtPlural(len, "day");
}
// todo: will our pissbaby american users beg us to add an option for their stupid ass incorrect date format
return fmt::format("{:&d.%m.&Y}", value);
}
Result<ServerDateTime> ServerDateTime::parse(std::string const& str) {
std::stringstream ss(str);
Value value;
if (std::chrono::from_stream(ss, "%FT%TZ", value)) {
return Ok(ServerDateTime {
.value = value,
});
}
return Err("Invalid date time format '{}'", str);
}
Result<ServerModVersion> ServerModVersion::parse(matjson::Value const& raw) { Result<ServerModVersion> ServerModVersion::parse(matjson::Value const& raw) {
auto json = raw; auto json = raw;
JsonChecker checker(json); JsonChecker checker(json);
@ -167,6 +203,12 @@ Result<ServerModMetadata> ServerModMetadata::parse(matjson::Value const& raw) {
root.needs("download_count").into(res.downloadCount); root.needs("download_count").into(res.downloadCount);
root.has("about").into(res.about); root.has("about").into(res.about);
root.has("changelog").into(res.changelog); root.has("changelog").into(res.changelog);
if (root.has("created_at")) {
GEODE_UNWRAP_INTO(res.createdAt, ServerDateTime::parse(root.has("created_at").template get<std::string>()));
}
if (root.has("updated_at")) {
GEODE_UNWRAP_INTO(res.updatedAt, ServerDateTime::parse(root.has("updated_at").template get<std::string>()));
}
std::vector<std::string> developerNames; std::vector<std::string> developerNames;
for (auto item : root.needs("developers").iterate()) { for (auto item : root.needs("developers").iterate()) {

View file

@ -7,6 +7,17 @@
using namespace geode::prelude; using namespace geode::prelude;
namespace server { namespace server {
struct ServerDateTime final {
using Clock = std::chrono::system_clock;
using Value = std::chrono::time_point<Clock>;
Value value;
std::string toAgoString() const;
static Result<ServerDateTime> parse(std::string const& str);
};
struct ServerDeveloper { struct ServerDeveloper {
std::string username; std::string username;
std::string displayName; std::string displayName;
@ -30,6 +41,8 @@ namespace server {
std::unordered_set<std::string> tags; std::unordered_set<std::string> tags;
std::optional<std::string> about; std::optional<std::string> about;
std::optional<std::string> changelog; std::optional<std::string> changelog;
std::optional<ServerDateTime> createdAt;
std::optional<ServerDateTime> updatedAt;
static Result<ServerModMetadata> parse(matjson::Value const& json); static Result<ServerModMetadata> parse(matjson::Value const& json);
}; };

View file

@ -66,10 +66,11 @@ bool ModPopup::setup(ModSource&& src) {
{ "GJ_downloadsIcon_001.png", "Downloads", "downloads", std::nullopt }, { "GJ_downloadsIcon_001.png", "Downloads", "downloads", std::nullopt },
{ "GJ_timeIcon_001.png", "Released", "release-date", std::nullopt }, { "GJ_timeIcon_001.png", "Released", "release-date", std::nullopt },
{ "GJ_timeIcon_001.png", "Updated", "update-date", std::nullopt }, { "GJ_timeIcon_001.png", "Updated", "update-date", std::nullopt },
{ "version.png"_spr, "Version", "version", src.getMetadata().getVersion().toString() }, { "version.png"_spr, "Version", "version", m_source.getMetadata().getVersion().toString() },
}) { }) {
auto container = CCNode::create(); auto container = CCNode::create();
container->setContentSize({ m_stats->getContentWidth(), 10 }); container->setContentSize({ m_stats->getContentWidth(), 10 });
container->setID(std::get<2>(stat));
auto iconSize = container->getContentHeight(); auto iconSize = container->getContentHeight();
auto icon = CCSprite::createWithSpriteFrameName(std::get<0>(stat)); auto icon = CCSprite::createWithSpriteFrameName(std::get<0>(stat));
@ -189,14 +190,22 @@ void ModPopup::setStatValue(CCNode* stat, std::string const& value) {
void ModPopup::onLoadServerInfo(PromiseEvent<server::ServerModMetadata, server::ServerError>* event) { void ModPopup::onLoadServerInfo(PromiseEvent<server::ServerModMetadata, server::ServerError>* event) {
if (auto data = event->getResolve()) { if (auto data = event->getResolve()) {
auto timeToString = [](auto const& time) {
if (time.has_value()) {
return time.value().toAgoString();
}
return std::string("N/A");
};
for (auto id : std::initializer_list<std::pair<const char*, std::string>> { for (auto id : std::initializer_list<std::pair<const char*, std::string>> {
{ "downloads", std::to_string(data->downloadCount) }, { "downloads", std::to_string(data->downloadCount) },
{ "release-date", "todo" }, { "release-date", timeToString(data->createdAt) },
{ "update-date", "todo" }, { "update-date", timeToString(data->updatedAt) },
}) { }) {
auto stat = m_stats->getChildByID(id.first); if (auto stat = m_stats->getChildByID(id.first)) {
stat->removeChildByID("loading-spinner"); stat->removeChildByID("loading-spinner");
this->setStatValue(stat, id.second); this->setStatValue(stat, id.second);
}
} }
} }
else if (auto err = event->getReject()) { else if (auto err = event->getReject()) {