use tag display names from server

This commit is contained in:
HJfod 2024-11-14 16:37:07 +02:00
parent 7c4e06d20c
commit 893b03e313
10 changed files with 93 additions and 68 deletions

View file

@ -246,6 +246,31 @@ std::string ServerDateTime::toAgoString() const {
return fmt::format("{:%b %d %Y}", value); return fmt::format("{:%b %d %Y}", value);
} }
Result<ServerTag> ServerTag::parse(matjson::Value const& raw) {
auto root = checkJson(raw, "ServerTag");
auto res = ServerTag();
root.needs("id").into(res.id);
root.needs("name").into(res.name);
root.needs("display_name").into(res.displayName);
return root.ok(res);
}
Result<std::vector<ServerTag>> ServerTag::parseList(matjson::Value const& raw) {
auto payload = checkJson(raw, "ServerTagsList");
std::vector<ServerTag> list {};
for (auto& item : payload.items()) {
auto mod = ServerTag::parse(item.json());
if (mod) {
list.push_back(mod.unwrap());
}
else {
log::error("Unable to parse tag from the server: {}", mod.unwrapErr());
}
}
return payload.ok(list);
}
Result<ServerDateTime> ServerDateTime::parse(std::string const& str) { Result<ServerDateTime> ServerDateTime::parse(std::string const& str) {
std::stringstream ss(str); std::stringstream ss(str);
date::sys_seconds seconds; date::sys_seconds seconds;
@ -690,33 +715,25 @@ ServerRequest<ByteVector> server::getModLogo(std::string const& id, bool useCach
); );
} }
ServerRequest<std::unordered_set<std::string>> server::getTags(bool useCache) { ServerRequest<std::vector<ServerTag>> server::getTags(bool useCache) {
if (useCache) { if (useCache) {
return getCache<getTags>().get(); return getCache<getTags>().get();
} }
auto req = web::WebRequest(); auto req = web::WebRequest();
req.userAgent(getServerUserAgent()); req.userAgent(getServerUserAgent());
return req.get(formatServerURL("/tags")).map( return req.get(formatServerURL("/detailed-tags")).map(
[](web::WebResponse* response) -> Result<std::unordered_set<std::string>, ServerError> { [](web::WebResponse* response) -> Result<std::vector<ServerTag>, ServerError> {
if (response->ok()) { if (response->ok()) {
// Parse payload // Parse payload
auto payload = parseServerPayload(*response); auto payload = parseServerPayload(*response);
if (!payload) { if (!payload) {
return Err(payload.unwrapErr()); return Err(payload.unwrapErr());
} }
matjson::Value json = payload.unwrap(); auto list = ServerTag::parseList(payload.unwrap());
if (!json.isArray()) { if (!list) {
return Err(ServerError(response->code(), "Expected a string array")); return Err(ServerError(response->code(), "Unable to parse response: {}", list.unwrapErr()));
} }
return Ok(list.unwrap());
std::unordered_set<std::string> tags;
for (auto item : json) {
if (!item.isString()) {
return Err(ServerError(response->code(), "Expected a string array"));
}
tags.insert(item.asString().unwrap());
}
return Ok(tags);
} }
return Err(parseServerError(*response)); return Err(parseServerError(*response));
}, },

View file

@ -10,6 +10,8 @@
using namespace geode::prelude; using namespace geode::prelude;
namespace server { namespace server {
// todo: replace parse()s with Serialize::fromJson now that it uses Results
struct ServerDateTime final { struct ServerDateTime final {
using Clock = std::chrono::system_clock; using Clock = std::chrono::system_clock;
using Value = std::chrono::time_point<Clock>; using Value = std::chrono::time_point<Clock>;
@ -21,6 +23,15 @@ namespace server {
static Result<ServerDateTime> parse(std::string const& str); static Result<ServerDateTime> parse(std::string const& str);
}; };
struct ServerTag final {
size_t id;
std::string name;
std::string displayName;
static Result<ServerTag> parse(matjson::Value const& json);
static Result<std::vector<ServerTag>> parseList(matjson::Value const& json);
};
struct ServerDeveloper final { struct ServerDeveloper final {
std::string username; std::string username;
std::string displayName; std::string displayName;
@ -147,7 +158,7 @@ namespace server {
ServerRequest<ServerModMetadata> getMod(std::string const& id, bool useCache = true); ServerRequest<ServerModMetadata> getMod(std::string const& id, bool useCache = true);
ServerRequest<ServerModVersion> getModVersion(std::string const& id, ModVersion const& version = ModVersionLatest(), bool useCache = true); ServerRequest<ServerModVersion> getModVersion(std::string const& id, ModVersion const& version = ModVersionLatest(), bool useCache = true);
ServerRequest<ByteVector> getModLogo(std::string const& id, bool useCache = true); ServerRequest<ByteVector> getModLogo(std::string const& id, bool useCache = true);
ServerRequest<std::unordered_set<std::string>> getTags(bool useCache = true); ServerRequest<std::vector<ServerTag>> getTags(bool useCache = true);
ServerRequest<std::optional<ServerModUpdate>> checkUpdates(Mod const* mod); ServerRequest<std::optional<ServerModUpdate>> checkUpdates(Mod const* mod);

View file

@ -196,10 +196,10 @@ ButtonSprite* createTagLabel(std::string const& text, std::pair<ccColor3B, ccCol
label->m_BGSprite->setColor(color.second); label->m_BGSprite->setColor(color.second);
return label; return label;
} }
ButtonSprite* createGeodeTagLabel(std::string_view tag) { ButtonSprite* createGeodeTagLabel(server::ServerTag const& tag) {
return createTagLabel(geodeTagName(tag), geodeTagColors(tag)); return createTagLabel(tag.displayName, geodeTagColors(tag));
} }
std::pair<ccColor3B, ccColor3B> geodeTagColors(std::string_view tag) { std::pair<ccColor3B, ccColor3B> geodeTagColors(server::ServerTag const& tag) {
static std::array TAG_COLORS { static std::array TAG_COLORS {
std::make_pair(ccc3(240, 233, 255), ccc3(130, 123, 163)), std::make_pair(ccc3(240, 233, 255), ccc3(130, 123, 163)),
std::make_pair(ccc3(234, 255, 245), ccc3(123, 163, 136)), std::make_pair(ccc3(234, 255, 245), ccc3(123, 163, 136)),
@ -207,20 +207,10 @@ std::pair<ccColor3B, ccColor3B> geodeTagColors(std::string_view tag) {
std::make_pair(ccc3(255, 253, 240), ccc3(163, 157, 123)), std::make_pair(ccc3(255, 253, 240), ccc3(163, 157, 123)),
std::make_pair(ccc3(255, 242, 240), ccc3(163, 128, 123)), std::make_pair(ccc3(255, 242, 240), ccc3(163, 128, 123)),
}; };
if (tag == "modtober24") { if (tag.name == "modtober24") {
return std::make_pair(ccc3(225, 236, 245), ccc3(82, 139, 201)); return std::make_pair(ccc3(225, 236, 245), ccc3(82, 139, 201));
} }
return TAG_COLORS[hash(tag) % 5932 % TAG_COLORS.size()]; return TAG_COLORS[hash(tag.name) % 5932 % TAG_COLORS.size()];
}
std::string geodeTagName(std::string_view tag) {
// todo in v4: rework tags to use a server-provided display name instead
if (tag == "modtober24") {
return "Modtober 2024";
}
// Everything else just capitalize and that's it
auto readable = std::string(tag);
readable[0] = std::toupper(readable[0]);
return readable;
} }
ListBorders* createGeodeListBorders(CCSize const& size, bool forceDisableTheme) { ListBorders* createGeodeListBorders(CCSize const& size, bool forceDisableTheme) {

View file

@ -7,6 +7,7 @@
#include <Geode/ui/BasedButtonSprite.hpp> #include <Geode/ui/BasedButtonSprite.hpp>
#include <Geode/ui/Popup.hpp> #include <Geode/ui/Popup.hpp>
#include <Geode/loader/Mod.hpp> #include <Geode/loader/Mod.hpp>
#include <server/Server.hpp>
using namespace geode::prelude; using namespace geode::prelude;
@ -87,9 +88,8 @@ ButtonSprite* createGeodeButton(std::string const& text, bool gold = false, Geod
CircleButtonSprite* createGeodeCircleButton(CCSprite* top, float scale = 1.f, CircleBaseSize size = CircleBaseSize::Medium, bool altColor = false, bool forceDisableTheme = false); CircleButtonSprite* createGeodeCircleButton(CCSprite* top, float scale = 1.f, CircleBaseSize size = CircleBaseSize::Medium, bool altColor = false, bool forceDisableTheme = false);
ButtonSprite* createTagLabel(std::string const& text, std::pair<ccColor3B, ccColor3B> const& color); ButtonSprite* createTagLabel(std::string const& text, std::pair<ccColor3B, ccColor3B> const& color);
ButtonSprite* createGeodeTagLabel(std::string_view tag); ButtonSprite* createGeodeTagLabel(server::ServerTag const& tag);
std::pair<ccColor3B, ccColor3B> geodeTagColors(std::string_view tag); std::pair<ccColor3B, ccColor3B> geodeTagColors(server::ServerTag const& tag);
std::string geodeTagName(std::string_view tag);
ListBorders* createGeodeListBorders(CCSize const& size, bool forceDisableTheme = false); ListBorders* createGeodeListBorders(CCSize const& size, bool forceDisableTheme = false);

View file

@ -144,7 +144,7 @@ bool FiltersPopup::setup(ModListSource* src) {
return true; return true;
} }
void FiltersPopup::onLoadTags(typename server::ServerRequest<std::unordered_set<std::string>>::Event* event) { void FiltersPopup::onLoadTags(typename server::ServerRequest<std::vector<server::ServerTag>>::Event* event) {
if (event->getValue() && event->getValue()->isOk()) { if (event->getValue() && event->getValue()->isOk()) {
auto tags = event->getValue()->unwrap(); auto tags = event->getValue()->unwrap();
m_tagsMenu->removeAllChildren(); m_tagsMenu->removeAllChildren();
@ -157,7 +157,7 @@ void FiltersPopup::onLoadTags(typename server::ServerRequest<std::unordered_set<
offSpr, onSpr, this, menu_selector(FiltersPopup::onSelectTag) offSpr, onSpr, this, menu_selector(FiltersPopup::onSelectTag)
); );
btn->m_notClickable = true; btn->m_notClickable = true;
btn->setUserObject("tag", CCString::create(tag)); btn->setUserObject("tag", CCString::create(tag.name));
m_tagsMenu->addChild(btn); m_tagsMenu->addChild(btn);
} }
m_tagsMenu->updateLayout(); m_tagsMenu->updateLayout();

View file

@ -13,14 +13,14 @@ protected:
ModListSource* m_source; ModListSource* m_source;
CCMenu* m_tagsMenu; CCMenu* m_tagsMenu;
std::unordered_set<std::string> m_selectedTags; std::unordered_set<std::string> m_selectedTags;
EventListener<server::ServerRequest<std::unordered_set<std::string>>> m_tagsListener; EventListener<server::ServerRequest<std::vector<server::ServerTag>>> m_tagsListener;
CCMenuItemToggler* m_enabledModsOnly = nullptr; CCMenuItemToggler* m_enabledModsOnly = nullptr;
TextInput* m_developerNameInput = nullptr; TextInput* m_developerNameInput = nullptr;
bool setup(ModListSource* src) override; bool setup(ModListSource* src) override;
void updateTags(); void updateTags();
void onLoadTags(typename server::ServerRequest<std::unordered_set<std::string>>::Event* event); void onLoadTags(typename server::ServerRequest<std::vector<server::ServerTag>>::Event* event);
void onResetTags(CCObject*); void onResetTags(CCObject*);
void onResetDevName(CCObject*); void onResetDevName(CCObject*);
void onSelectTag(CCObject* sender); void onSelectTag(CCObject* sender);

View file

@ -885,7 +885,7 @@ void ModPopup::onCheckUpdates(typename server::ServerRequest<std::optional<serve
} }
} }
void ModPopup::onLoadTags(typename server::ServerRequest<std::unordered_set<std::string>>::Event* event) { void ModPopup::onLoadTags(typename server::ServerRequest<std::vector<server::ServerTag>>::Event* event) {
if (event->getValue() && event->getValue()->isOk()) { if (event->getValue() && event->getValue()->isOk()) {
auto data = event->getValue()->unwrap(); auto data = event->getValue()->unwrap();
m_tags->removeAllChildren(); m_tags->removeAllChildren();
@ -904,7 +904,7 @@ void ModPopup::onLoadTags(typename server::ServerRequest<std::unordered_set<std:
// If the build times from the cool popup become too long then we can // If the build times from the cool popup become too long then we can
// probably move that to a normal FLAlert that explains "Modtober was // probably move that to a normal FLAlert that explains "Modtober was
// this contest blah blah this mod was made for it" // this contest blah blah this mod was made for it"
else if (data.contains("modtober24")) { else if (ranges::contains(data, [](auto const& tag) { return tag.name == "modtober24"; })) {
auto menu = CCMenu::create(); auto menu = CCMenu::create();
menu->setID("modtober-banner"); menu->setID("modtober-banner");
menu->ignoreAnchorPointForPosition(false); menu->ignoreAnchorPointForPosition(false);

View file

@ -38,7 +38,7 @@ protected:
CCNode* m_modtoberBanner = nullptr; CCNode* m_modtoberBanner = nullptr;
std::unordered_map<Tab, std::pair<GeodeTabSprite*, Ref<CCNode>>> m_tabs; std::unordered_map<Tab, std::pair<GeodeTabSprite*, Ref<CCNode>>> m_tabs;
EventListener<server::ServerRequest<server::ServerModMetadata>> m_statsListener; EventListener<server::ServerRequest<server::ServerModMetadata>> m_statsListener;
EventListener<server::ServerRequest<std::unordered_set<std::string>>> m_tagsListener; EventListener<server::ServerRequest<std::vector<server::ServerTag>>> m_tagsListener;
EventListener<server::ServerRequest<std::optional<server::ServerModUpdate>>> m_checkUpdateListener; EventListener<server::ServerRequest<std::optional<server::ServerModUpdate>>> m_checkUpdateListener;
EventListener<UpdateModListStateFilter> m_updateStateListener; EventListener<UpdateModListStateFilter> m_updateStateListener;
EventListener<server::ModDownloadFilter> m_downloadListener; EventListener<server::ModDownloadFilter> m_downloadListener;
@ -52,7 +52,7 @@ protected:
void setStatValue(CCNode* stat, std::optional<std::string> const& value); void setStatValue(CCNode* stat, std::optional<std::string> const& value);
void onLoadServerInfo(typename server::ServerRequest<server::ServerModMetadata>::Event* event); void onLoadServerInfo(typename server::ServerRequest<server::ServerModMetadata>::Event* event);
void onLoadTags(typename server::ServerRequest<std::unordered_set<std::string>>::Event* event); void onLoadTags(typename server::ServerRequest<std::vector<server::ServerTag>>::Event* event);
void onCheckUpdates(typename server::ServerRequest<std::optional<server::ServerModUpdate>>::Event* event); void onCheckUpdates(typename server::ServerRequest<std::optional<server::ServerModUpdate>>::Event* event);
void onTab(CCObject* sender); void onTab(CCObject* sender);

View file

@ -163,34 +163,41 @@ server::ServerRequest<server::ServerModMetadata> ModSource::fetchServerInfo() co
// should deal with performance issues // should deal with performance issues
return server::getMod(this->getID()); return server::getMod(this->getID());
} }
server::ServerRequest<std::unordered_set<std::string>> ModSource::fetchValidTags() const { server::ServerRequest<std::vector<server::ServerTag>> ModSource::fetchValidTags() const {
return std::visit(makeVisitor { std::unordered_set<std::string> modTags;
[](server::ServerModMetadata const& metadata) { std::visit(makeVisitor {
// Server info tags are always certain to be valid since the server has already validated them [&](Mod* mod) {
return server::ServerRequest<std::unordered_set<std::string>>::immediate(Ok(metadata.tags)); modTags = mod->getMetadata().getTags();
}, },
[this](auto const&) { [&](server::ServerModMetadata const& metadata) {
return server::getTags().map( modTags = metadata.tags;
[modTags = this->getMetadata().getTags()](auto* result) -> Result<std::unordered_set<std::string>, server::ServerError> {
if (result->isOk()) {
std::unordered_set<std::string> fetched = result->unwrap();
// Filter out invalid tags
auto finalTags = std::unordered_set<std::string>();
for (auto& tag : modTags) {
if (result->unwrap().contains(tag)) {
finalTags.insert(tag);
}
}
}
return Ok(finalTags);
},
[](server::ServerProgress* progress) {
return *progress;
}
);
}, },
}, m_value); }, m_value);
// This does two things:
// 1. For installed mods, it filters out invalid tags
// 2. For everything else, it gets the rest of the tag info (display name) from the server
return server::getTags().map(
[modTags = std::move(modTags)](auto* result) -> Result<std::vector<server::ServerTag>, server::ServerError> {
auto finalTags = std::vector<server::ServerTag>();
if (result->isOk()) {
auto fetched = result->unwrap();
// Filter out invalid tags
for (auto& tag : modTags) {
auto stag = ranges::find(fetched, [&tag](server::ServerTag const& stag) {
return stag.name == tag;
});
if (stag) {
finalTags.push_back(*stag);
}
}
}
return Ok(finalTags);
},
[](server::ServerProgress* progress) {
return *progress;
}
);
} }
server::ServerRequest<std::optional<server::ServerModUpdate>> ModSource::checkUpdates() { server::ServerRequest<std::optional<server::ServerModUpdate>> ModSource::checkUpdates() {
m_availableUpdate = std::nullopt; m_availableUpdate = std::nullopt;

View file

@ -47,7 +47,7 @@ public:
server::ServerRequest<server::ServerModMetadata> fetchServerInfo() const; server::ServerRequest<server::ServerModMetadata> fetchServerInfo() const;
server::ServerRequest<std::optional<std::string>> fetchAbout() const; server::ServerRequest<std::optional<std::string>> fetchAbout() const;
server::ServerRequest<std::optional<std::string>> fetchChangelog() const; server::ServerRequest<std::optional<std::string>> fetchChangelog() const;
server::ServerRequest<std::unordered_set<std::string>> fetchValidTags() const; server::ServerRequest<std::vector<server::ServerTag>> fetchValidTags() const;
server::ServerRequest<std::optional<server::ServerModUpdate>> checkUpdates(); server::ServerRequest<std::optional<server::ServerModUpdate>> checkUpdates();
void startInstall(); void startInstall();
}; };