diff --git a/loader/src/server/Server.cpp b/loader/src/server/Server.cpp index c8a2f866..904787a6 100644 --- a/loader/src/server/Server.cpp +++ b/loader/src/server/Server.cpp @@ -1,6 +1,7 @@ #include "Server.hpp" #include #include +#include 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(now - value).count(); + if (len < 60) { + return fmtPlural(len, "minute"); + } + len = std::chrono::duration_cast(now - value).count(); + if (len < 24) { + return fmtPlural(len, "hour"); + } + len = std::chrono::duration_cast(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::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::parse(matjson::Value const& raw) { auto json = raw; JsonChecker checker(json); @@ -167,6 +203,12 @@ Result ServerModMetadata::parse(matjson::Value const& raw) { root.needs("download_count").into(res.downloadCount); root.has("about").into(res.about); root.has("changelog").into(res.changelog); + if (root.has("created_at")) { + GEODE_UNWRAP_INTO(res.createdAt, ServerDateTime::parse(root.has("created_at").template get())); + } + if (root.has("updated_at")) { + GEODE_UNWRAP_INTO(res.updatedAt, ServerDateTime::parse(root.has("updated_at").template get())); + } std::vector developerNames; for (auto item : root.needs("developers").iterate()) { diff --git a/loader/src/server/Server.hpp b/loader/src/server/Server.hpp index 21aacafd..d1bb0e7d 100644 --- a/loader/src/server/Server.hpp +++ b/loader/src/server/Server.hpp @@ -7,6 +7,17 @@ using namespace geode::prelude; namespace server { + struct ServerDateTime final { + using Clock = std::chrono::system_clock; + using Value = std::chrono::time_point; + + Value value; + + std::string toAgoString() const; + + static Result parse(std::string const& str); + }; + struct ServerDeveloper { std::string username; std::string displayName; @@ -30,6 +41,8 @@ namespace server { std::unordered_set tags; std::optional about; std::optional changelog; + std::optional createdAt; + std::optional updatedAt; static Result parse(matjson::Value const& json); }; diff --git a/loader/src/ui/mods/ModPopup.cpp b/loader/src/ui/mods/ModPopup.cpp index 55632ac6..e8076fd3 100644 --- a/loader/src/ui/mods/ModPopup.cpp +++ b/loader/src/ui/mods/ModPopup.cpp @@ -66,10 +66,11 @@ bool ModPopup::setup(ModSource&& src) { { "GJ_downloadsIcon_001.png", "Downloads", "downloads", std::nullopt }, { "GJ_timeIcon_001.png", "Released", "release-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(); container->setContentSize({ m_stats->getContentWidth(), 10 }); + container->setID(std::get<2>(stat)); auto iconSize = container->getContentHeight(); 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* event) { 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> { { "downloads", std::to_string(data->downloadCount) }, - { "release-date", "todo" }, - { "update-date", "todo" }, + { "release-date", timeToString(data->createdAt) }, + { "update-date", timeToString(data->updatedAt) }, }) { - auto stat = m_stats->getChildByID(id.first); - stat->removeChildByID("loading-spinner"); - this->setStatValue(stat, id.second); + if (auto stat = m_stats->getChildByID(id.first)) { + stat->removeChildByID("loading-spinner"); + this->setStatValue(stat, id.second); + } } } else if (auto err = event->getReject()) {