From a11e442f1d73ff6f08ff4862c27d707fbb614d39 Mon Sep 17 00:00:00 2001 From: Andrew <159703254+andrewsoonqn@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:01:21 +0800 Subject: [PATCH] Add version sorting for plugins Enhance plugin dialog with semver-aware sorting by version, enabling users to sort plugins based on version comparisons. --- resources/web/dialog/PluginsDialog/index.html | 3 ++- .../web/dialog/PluginsDialog/plugin-sort.js | 2 +- src/slic3r/GUI/PluginSort.hpp | 25 +++++++++++++++++++ src/slic3r/GUI/PluginsDialog.cpp | 4 +++ tests/slic3rutils/test_plugin_sort.cpp | 20 +++++++++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/resources/web/dialog/PluginsDialog/index.html b/resources/web/dialog/PluginsDialog/index.html index 438f6eb4e9..853d1a8464 100644 --- a/resources/web/dialog/PluginsDialog/index.html +++ b/resources/web/dialog/PluginsDialog/index.html @@ -47,7 +47,8 @@ Activate Name - Plugin Version + Plugin Version Source #include #include @@ -16,6 +18,7 @@ namespace Slic3r::GUI Status, Name, Source, + Version, // why: neutral "no column selected" state - clearing a header sort returns here and the // list falls to compare_plugin_base_order only. Header UI reaches it via the asc/desc/clear cycle. None @@ -34,6 +37,7 @@ namespace Slic3r::GUI case PluginSortKey::Status: return "status"; case PluginSortKey::Name: return "name"; case PluginSortKey::Source: return "source"; + case PluginSortKey::Version: return "version"; case PluginSortKey::None: return "none"; } @@ -53,6 +57,8 @@ namespace Slic3r::GUI return PluginSortKey::Name; if (sort_key == "source") return PluginSortKey::Source; + if (sort_key == "version") + return PluginSortKey::Version; if (sort_key == "none") return PluginSortKey::None; return fallback; @@ -141,6 +147,23 @@ namespace Slic3r::GUI return lhs.plugin_key.compare(rhs.plugin_key); } + // Compares two version strings returning -1 / 0 / +1. Uses Slic3r::Semver (the same parser the + // plugin catalog's update-available check uses); on unparseable input falls back to the natural + // compare so the order stays deterministic. + // e.g. "1.2.0" < "1.10.0" (numeric), "1.0.0-rc1" < "1.0.0" (semver prerelease rule). + inline int compare_plugin_version(const std::string& lhs, const std::string& rhs) + { + const auto lhs_semver = Semver::parse(lhs); + const auto rhs_semver = Semver::parse(rhs); + if (lhs_semver && rhs_semver) + { + if (*lhs_semver < *rhs_semver) return -1; + if (*rhs_semver < *lhs_semver) return 1; + return 0; + } + return compare_ascii_case_insensitive_natural(lhs, rhs); + } + // Compares two items by the chosen primary key, returning -1 / 0 / +1. Status and Source // rank by enum ordinal (the declared dialog priority); Name uses the natural compare above. // e.g. Status: an enabled item (lower ordinal) sorts before a disabled one. @@ -157,6 +180,8 @@ namespace Slic3r::GUI return compare_ascii_case_insensitive_natural(lhs.display_name, rhs.display_name); case PluginSortKey::Source: return static_cast(lhs.source) - static_cast(rhs.source); + case PluginSortKey::Version: + return compare_plugin_version(lhs.sort_version, rhs.sort_version); case PluginSortKey::None: // why: no primary key - every pair ties here so sort_plugin_items_for_dialog falls // straight to the ascending base order (direction is irrelevant for the baseline). diff --git a/src/slic3r/GUI/PluginsDialog.cpp b/src/slic3r/GUI/PluginsDialog.cpp index bed37f2ebe..070bc36a14 100644 --- a/src/slic3r/GUI/PluginsDialog.cpp +++ b/src/slic3r/GUI/PluginsDialog.cpp @@ -89,6 +89,7 @@ struct PluginDialogItem std::string version; std::string installed_version; std::string latest_version; + std::string sort_version; // Version shown in the row (installed if installed, else latest); used by the Version sort. std::string type_label; std::string type_key; std::string sharing_token; @@ -319,6 +320,9 @@ PluginDialogItem build_plugin_dialog_item(const PluginDescriptor& descriptor) (descriptor.installed_version.empty() ? descriptor.version : descriptor.installed_version) : std::string{}; item.latest_version = descriptor.latest_available_version(); + // why: sort by the same version the row displays (GetDisplayVersion in index.js) - installed when + // installed, otherwise latest - so the Version sort matches what the user sees. + item.sort_version = item.installed_version.empty() ? item.latest_version : item.installed_version; item.type_label = descriptor.type_label(); item.type_key = plugin_capability_type_to_string(descriptor.primary_capability_type()); // "types" is the display-only compatibility list. Cloud plugins show the raw labels the diff --git a/tests/slic3rutils/test_plugin_sort.cpp b/tests/slic3rutils/test_plugin_sort.cpp index 98ac71c656..2c1b3b7a2c 100644 --- a/tests/slic3rutils/test_plugin_sort.cpp +++ b/tests/slic3rutils/test_plugin_sort.cpp @@ -23,6 +23,7 @@ struct SortFixtureItem PluginStatus status; std::string type_key; std::string display_name; + std::string sort_version; }; std::vector keys(const std::vector& items) @@ -90,6 +91,25 @@ TEST_CASE("plugin dialog source sort uses enum priority", "[plugin][sort]") CHECK(keys(items) == desc_expected); } +TEST_CASE("plugin dialog version sort is semver-aware with base-order ties", "[plugin][sort]") +{ + std::vector items = { + {"v_1_2_0", PluginSource::Local, PluginStatus::Activated, "script", "B", "1.2.0"}, + {"v_1_10_0", PluginSource::Local, PluginStatus::Activated, "script", "A", "1.10.0"}, + {"v_0_9_3", PluginSource::Local, PluginStatus::Activated, "script", "C", "0.9.3"}, + }; + + sort_plugin_items_for_dialog(items, PluginSortKey::Version, PluginSortOrder::Asc); + // why: semver numeric compare - 1.10.0 > 1.2.0 (not lexical "1.10" < "1.2"), so ascending is + // 0.9.3 < 1.2.0 < 1.10.0. + const std::vector asc_expected = {"v_0_9_3", "v_1_2_0", "v_1_10_0"}; + CHECK(keys(items) == asc_expected); + + sort_plugin_items_for_dialog(items, PluginSortKey::Version, PluginSortOrder::Desc); + const std::vector desc_expected = {"v_1_10_0", "v_1_2_0", "v_0_9_3"}; + CHECK(keys(items) == desc_expected); +} + TEST_CASE("plugin dialog name sort is case-insensitive and numeric-aware", "[plugin][sort]") { std::vector items = {