Skip to content

Commit 8e1a549

Browse files
pynickleGlavo
andauthored
feat: modrinth loader tag aggregate display (#5964)
Co-authored-by: Glavo <zjx001202@gmail.com>
1 parent 1d18eae commit 8e1a549

2 files changed

Lines changed: 141 additions & 6 deletions

File tree

HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,7 @@
2424
import org.jackhuang.hmcl.mod.ModLoaderType;
2525
import org.jackhuang.hmcl.mod.RemoteMod;
2626
import org.jackhuang.hmcl.mod.RemoteModRepository;
27-
import org.jackhuang.hmcl.util.DigestUtils;
28-
import org.jackhuang.hmcl.util.Immutable;
29-
import org.jackhuang.hmcl.util.Lang;
30-
import org.jackhuang.hmcl.util.Pair;
31-
import org.jackhuang.hmcl.util.StringUtils;
27+
import org.jackhuang.hmcl.util.*;
3228
import org.jackhuang.hmcl.util.gson.JsonUtils;
3329
import org.jackhuang.hmcl.util.io.HttpRequest;
3430
import org.jackhuang.hmcl.util.io.NetworkUtils;
@@ -42,6 +38,7 @@
4238
import java.nio.file.Path;
4339
import java.time.Instant;
4440
import java.util.ArrayList;
41+
import java.util.Comparator;
4542
import java.util.Collections;
4643
import java.util.List;
4744
import java.util.Map;
@@ -63,6 +60,41 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
6360
public static final ModrinthRemoteModRepository RESOURCE_PACKS = new ModrinthRemoteModRepository("resourcepack");
6461
public static final ModrinthRemoteModRepository SHADER_PACKS = new ModrinthRemoteModRepository("shader");
6562

63+
private static final Comparator<String> TAG_COMPARATOR = PriorityComparator.of(
64+
List.of("babric",
65+
"bta-babric",
66+
"bukkit",
67+
"bungeecord",
68+
"canvas",
69+
"datapack",
70+
"fabric",
71+
"folia",
72+
"forge",
73+
"geyser",
74+
"iris",
75+
"java-agent",
76+
"legacy-fabric",
77+
"liteloader",
78+
"minecraft",
79+
"modloader",
80+
"mrpack",
81+
"neoforge",
82+
"nilloader",
83+
"optifine",
84+
"ornith",
85+
"paper",
86+
"purpur",
87+
"quilt",
88+
"rift",
89+
"spigot",
90+
"sponge",
91+
"vanilla",
92+
"velocity",
93+
"waterfall"),
94+
Comparator.naturalOrder(),
95+
false
96+
);
97+
6698
private static final Semaphore SEMAPHORE = new Semaphore(16);
6799

68100
private static final String PREFIX = "https://api.modrinth.com";
@@ -95,6 +127,12 @@ private static String convertSortType(SortType sortType) {
95127
}
96128
}
97129

130+
static List<String> sortDisplayCategories(List<String> displayCategories) {
131+
return displayCategories != null && !displayCategories.isEmpty()
132+
? displayCategories.stream().sorted(TAG_COMPARATOR).toList()
133+
: List.of();
134+
}
135+
98136
@Override
99137
public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
100138
SEMAPHORE.acquireUninterruptibly();
@@ -811,7 +849,7 @@ public RemoteMod toMod() {
811849
author,
812850
title,
813851
description,
814-
displayCategories,
852+
sortDisplayCategories(displayCategories),
815853
String.format("https://modrinth.com/%s/%s", projectType, projectId),
816854
iconUrl,
817855
this
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Hello Minecraft! Launcher
3+
* Copyright (C) 2026 huangyuhui <huanghongxun2008@126.com> and contributors
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
package org.jackhuang.hmcl.util;
19+
20+
import org.jetbrains.annotations.NotNull;
21+
22+
import java.util.*;
23+
24+
/// A comparator that orders elements based on a predefined priority map,
25+
/// falling back to another comparator for elements not present in the map.
26+
///
27+
/// Elements found in the priority map are compared by their associated priority values.
28+
/// When only one of the two elements has a priority, the `prioritizedFirst` flag
29+
/// determines whether the prioritized element is ordered before or after the other.
30+
///
31+
/// @param <T> the type of elements to be compared
32+
public final class PriorityComparator<T> implements Comparator<T> {
33+
34+
/// Creates a new `PriorityComparator` with a priority map and a fallback comparator.
35+
/// Elements with a defined priority are ordered before those without.
36+
public static <T extends Comparable<T>> PriorityComparator<T> of(T... values) {
37+
Map<T, Integer> priorities = new HashMap<>();
38+
for (int i = 0; i < values.length; i++)
39+
priorities.put(values[i], i);
40+
return new PriorityComparator<>(priorities, Comparator.naturalOrder(), true);
41+
}
42+
43+
/// Creates a new `PriorityComparator` with a priority map, a fallback comparator, and a flag to determine the order of prioritized elements.
44+
/// Elements with a defined priority are ordered before those without if `prioritizedFirst` is `true`, otherwise they are ordered after.
45+
public static <T> PriorityComparator<T> of(List<T> values, Comparator<? super T> fallback, boolean prioritizedFirst) {
46+
Objects.requireNonNull(fallback);
47+
48+
Map<T, Integer> priorities = new HashMap<>();
49+
for (int i = 0; i < values.size(); i++)
50+
priorities.put(values.get(i), i);
51+
return new PriorityComparator<>(priorities, fallback, prioritizedFirst);
52+
}
53+
54+
/// A mapping from values to their priority. Lower values indicate higher priority.
55+
private final @NotNull Map<T, Integer> priorities;
56+
57+
/// The fallback comparator used when neither element has a defined priority.
58+
private final @NotNull Comparator<? super T> fallback;
59+
60+
/// If `true`, elements with a defined priority are ordered before those without;
61+
/// if `false`, they are ordered after.
62+
private final boolean prioritizedFirst;
63+
64+
/// Creates a new `PriorityComparator`.
65+
///
66+
/// @param priorities a map from values to their priority (lower value = higher priority)
67+
/// @param fallback the comparator to use when neither element has a defined priority
68+
/// @param prioritizedFirst if `true`, prioritized elements come before non-prioritized ones
69+
private PriorityComparator(
70+
Map<T, Integer> priorities,
71+
Comparator<? super T> fallback,
72+
boolean prioritizedFirst) {
73+
this.priorities = priorities;
74+
this.fallback = fallback;
75+
this.prioritizedFirst = prioritizedFirst;
76+
}
77+
78+
@Override
79+
public int compare(T value1, T value2) {
80+
Integer p1 = priorities.get(value1);
81+
Integer p2 = priorities.get(value2);
82+
83+
if (p1 != null) {
84+
if (p2 != null) {
85+
return p1.compareTo(p2);
86+
}
87+
88+
return prioritizedFirst ? -1 : 1;
89+
} else {
90+
if (p2 != null) {
91+
return prioritizedFirst ? 1 : -1;
92+
} else {
93+
return fallback.compare(value1, value2);
94+
}
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)