mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-01 07:12:07 +00:00
Add version sorting for plugins
Enhance plugin dialog with semver-aware sorting by version, enabling users to sort plugins based on version comparisons.
This commit is contained in:
@@ -47,7 +47,8 @@
|
|||||||
<span>Activate</span>
|
<span>Activate</span>
|
||||||
<span class="sort-th" data-sort-field="name" role="button" tabindex="0"
|
<span class="sort-th" data-sort-field="name" role="button" tabindex="0"
|
||||||
title="Sort by name">Name<span class="sort-tri" aria-hidden="true"></span></span>
|
title="Sort by name">Name<span class="sort-tri" aria-hidden="true"></span></span>
|
||||||
<span>Plugin Version</span>
|
<span class="sort-th" data-sort-field="version" role="button" tabindex="0"
|
||||||
|
title="Sort by version">Plugin Version<span class="sort-tri" aria-hidden="true"></span></span>
|
||||||
<span class="sort-th" data-sort-field="source" role="button" tabindex="0"
|
<span class="sort-th" data-sort-field="source" role="button" tabindex="0"
|
||||||
title="Sort by source">Source<span class="sort-tri" aria-hidden="true"></span></span>
|
title="Sort by source">Source<span class="sort-tri" aria-hidden="true"></span></span>
|
||||||
<span class="sort-th" data-sort-field="status" role="button" tabindex="0"
|
<span class="sort-th" data-sort-field="status" role="button" tabindex="0"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
const DEFAULT_PLUGIN_SORT = { key: "none", order: "asc" };
|
const DEFAULT_PLUGIN_SORT = { key: "none", order: "asc" };
|
||||||
// note: SORT_FIELDS are the clickable columns. "none" is the baseline/cleared state, not a field -
|
// note: SORT_FIELDS are the clickable columns. "none" is the baseline/cleared state, not a field -
|
||||||
// it is special-cased in NormalizePluginSort and produced by CyclePluginSort's third click.
|
// it is special-cased in NormalizePluginSort and produced by CyclePluginSort's third click.
|
||||||
const SORT_FIELDS = new Set(["status", "name", "source"]);
|
const SORT_FIELDS = new Set(["status", "name", "source", "version"]);
|
||||||
let pluginSort = { ...DEFAULT_PLUGIN_SORT };
|
let pluginSort = { ...DEFAULT_PLUGIN_SORT };
|
||||||
|
|
||||||
// why: C++ returns canonical sort state; guard stale or malformed values before reflecting them.
|
// why: C++ returns canonical sort state; guard stale or malformed values before reflecting them.
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include "PluginSource.hpp"
|
#include "PluginSource.hpp"
|
||||||
#include "PluginStatus.hpp"
|
#include "PluginStatus.hpp"
|
||||||
|
|
||||||
|
#include "libslic3r/Semver.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -16,6 +18,7 @@ namespace Slic3r::GUI
|
|||||||
Status,
|
Status,
|
||||||
Name,
|
Name,
|
||||||
Source,
|
Source,
|
||||||
|
Version,
|
||||||
// why: neutral "no column selected" state - clearing a header sort returns here and the
|
// 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.
|
// list falls to compare_plugin_base_order only. Header UI reaches it via the asc/desc/clear cycle.
|
||||||
None
|
None
|
||||||
@@ -34,6 +37,7 @@ namespace Slic3r::GUI
|
|||||||
case PluginSortKey::Status: return "status";
|
case PluginSortKey::Status: return "status";
|
||||||
case PluginSortKey::Name: return "name";
|
case PluginSortKey::Name: return "name";
|
||||||
case PluginSortKey::Source: return "source";
|
case PluginSortKey::Source: return "source";
|
||||||
|
case PluginSortKey::Version: return "version";
|
||||||
case PluginSortKey::None: return "none";
|
case PluginSortKey::None: return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +57,8 @@ namespace Slic3r::GUI
|
|||||||
return PluginSortKey::Name;
|
return PluginSortKey::Name;
|
||||||
if (sort_key == "source")
|
if (sort_key == "source")
|
||||||
return PluginSortKey::Source;
|
return PluginSortKey::Source;
|
||||||
|
if (sort_key == "version")
|
||||||
|
return PluginSortKey::Version;
|
||||||
if (sort_key == "none")
|
if (sort_key == "none")
|
||||||
return PluginSortKey::None;
|
return PluginSortKey::None;
|
||||||
return fallback;
|
return fallback;
|
||||||
@@ -141,6 +147,23 @@ namespace Slic3r::GUI
|
|||||||
return lhs.plugin_key.compare(rhs.plugin_key);
|
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
|
// 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.
|
// 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.
|
// 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);
|
return compare_ascii_case_insensitive_natural(lhs.display_name, rhs.display_name);
|
||||||
case PluginSortKey::Source:
|
case PluginSortKey::Source:
|
||||||
return static_cast<int>(lhs.source) - static_cast<int>(rhs.source);
|
return static_cast<int>(lhs.source) - static_cast<int>(rhs.source);
|
||||||
|
case PluginSortKey::Version:
|
||||||
|
return compare_plugin_version(lhs.sort_version, rhs.sort_version);
|
||||||
case PluginSortKey::None:
|
case PluginSortKey::None:
|
||||||
// why: no primary key - every pair ties here so sort_plugin_items_for_dialog falls
|
// 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).
|
// straight to the ascending base order (direction is irrelevant for the baseline).
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ struct PluginDialogItem
|
|||||||
std::string version;
|
std::string version;
|
||||||
std::string installed_version;
|
std::string installed_version;
|
||||||
std::string latest_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_label;
|
||||||
std::string type_key;
|
std::string type_key;
|
||||||
std::string sharing_token;
|
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) :
|
(descriptor.installed_version.empty() ? descriptor.version : descriptor.installed_version) :
|
||||||
std::string{};
|
std::string{};
|
||||||
item.latest_version = descriptor.latest_available_version();
|
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_label = descriptor.type_label();
|
||||||
item.type_key = plugin_capability_type_to_string(descriptor.primary_capability_type());
|
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
|
// "types" is the display-only compatibility list. Cloud plugins show the raw labels the
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ struct SortFixtureItem
|
|||||||
PluginStatus status;
|
PluginStatus status;
|
||||||
std::string type_key;
|
std::string type_key;
|
||||||
std::string display_name;
|
std::string display_name;
|
||||||
|
std::string sort_version;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<std::string> keys(const std::vector<SortFixtureItem>& items)
|
std::vector<std::string> keys(const std::vector<SortFixtureItem>& items)
|
||||||
@@ -90,6 +91,25 @@ TEST_CASE("plugin dialog source sort uses enum priority", "[plugin][sort]")
|
|||||||
CHECK(keys(items) == desc_expected);
|
CHECK(keys(items) == desc_expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("plugin dialog version sort is semver-aware with base-order ties", "[plugin][sort]")
|
||||||
|
{
|
||||||
|
std::vector<SortFixtureItem> 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<std::string> 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<std::string> 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]")
|
TEST_CASE("plugin dialog name sort is case-insensitive and numeric-aware", "[plugin][sort]")
|
||||||
{
|
{
|
||||||
std::vector<SortFixtureItem> items = {
|
std::vector<SortFixtureItem> items = {
|
||||||
|
|||||||
Reference in New Issue
Block a user