* Fix #3065 * Fix * Fix.
This commit is contained in:
@@ -43,7 +43,6 @@ import org.jackhuang.hmcl.mod.RemoteModRepository;
|
|||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||||
@@ -52,6 +51,7 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
|||||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||||
import org.jackhuang.hmcl.util.AggregatedObservableList;
|
import org.jackhuang.hmcl.util.AggregatedObservableList;
|
||||||
|
import org.jackhuang.hmcl.util.Holder;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||||
@@ -64,10 +64,10 @@ import java.util.Locale;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
|
||||||
|
|
||||||
public class DownloadListPage extends Control implements DecoratorPage, VersionPage.VersionLoadable {
|
public class DownloadListPage extends Control implements DecoratorPage, VersionPage.VersionLoadable {
|
||||||
protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
|
||||||
@@ -87,7 +87,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
protected final ListProperty<String> downloadSources = new SimpleListProperty<>(this, "downloadSources", FXCollections.observableArrayList());
|
protected final ListProperty<String> downloadSources = new SimpleListProperty<>(this, "downloadSources", FXCollections.observableArrayList());
|
||||||
protected final StringProperty downloadSource = new SimpleStringProperty();
|
protected final StringProperty downloadSource = new SimpleStringProperty();
|
||||||
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||||
private TaskExecutor executor;
|
private int searchID = 0;
|
||||||
protected RemoteModRepository repository;
|
protected RemoteModRepository repository;
|
||||||
|
|
||||||
private Runnable retrySearch;
|
private Runnable retrySearch;
|
||||||
@@ -163,11 +163,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setFailed(false);
|
setFailed(false);
|
||||||
|
|
||||||
if (executor != null && !executor.isCancelled()) {
|
int currentSearchID = searchID = searchID + 1;
|
||||||
executor.cancel();
|
Task.supplyAsync(() -> {
|
||||||
}
|
|
||||||
|
|
||||||
executor = Task.supplyAsync(() -> {
|
|
||||||
Profile.ProfileVersion version = this.version.get();
|
Profile.ProfileVersion version = this.version.get();
|
||||||
if (StringUtils.isBlank(version.getVersion())) {
|
if (StringUtils.isBlank(version.getVersion())) {
|
||||||
return userGameVersion;
|
return userGameVersion;
|
||||||
@@ -176,9 +173,11 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
? version.getProfile().getRepository().getGameVersion(version.getVersion()).orElse("")
|
? version.getProfile().getRepository().getGameVersion(version.getVersion()).orElse("")
|
||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
}).thenApplyAsync(gameVersion ->
|
}).thenApplyAsync(gameVersion -> repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC)).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||||
repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC)
|
if (searchID != currentSearchID) {
|
||||||
).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (exception == null) {
|
if (exception == null) {
|
||||||
items.setAll(result.getResults().collect(Collectors.toList()));
|
items.setAll(result.getResults().collect(Collectors.toList()));
|
||||||
@@ -196,7 +195,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
return i18n("curse.category." + category);
|
return i18n("curse.category." + category);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getLocalizedCategoryIndent(ModDownloadListPageSkin.CategoryIndented category) {
|
private String getLocalizedCategoryIndent(ModDownloadListPageSkin.CategoryIndented category) {
|
||||||
return StringUtils.repeats(' ', category.indent * 4) +
|
return StringUtils.repeats(' ', category.indent * 4) +
|
||||||
(category.getCategory() == null
|
(category.getCategory() == null
|
||||||
? i18n("curse.category.0")
|
? i18n("curse.category.0")
|
||||||
@@ -344,13 +343,14 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
sortComboBox.getSelectionModel().select(0);
|
sortComboBox.getSelectionModel().select(0);
|
||||||
searchPane.addRow(rowIndex++, new Label(i18n("mods.category")), categoryStackPane, new Label(i18n("search.sort")), sortStackPane);
|
searchPane.addRow(rowIndex++, new Label(i18n("mods.category")), categoryStackPane, new Label(i18n("search.sort")), sortStackPane);
|
||||||
|
|
||||||
StringProperty previousSearchFilter = new SimpleStringProperty(this, "Previous Seach Filter", "");
|
IntegerProperty filterID = new SimpleIntegerProperty(this, "Filter ID", 0);
|
||||||
|
IntegerProperty currentFilterID = new SimpleIntegerProperty(this, "Current Filter ID", -1);
|
||||||
EventHandler<ActionEvent> searchAction = e -> {
|
EventHandler<ActionEvent> searchAction = e -> {
|
||||||
if (!previousSearchFilter.get().equals(nameField.getText())) {
|
if (currentFilterID.get() != filterID.get()) {
|
||||||
control.pageOffset.set(0);
|
control.pageOffset.set(0);
|
||||||
}
|
}
|
||||||
|
currentFilterID.set(filterID.get());
|
||||||
|
|
||||||
previousSearchFilter.set(nameField.getText());
|
|
||||||
getSkinnable().search(gameVersionField.getSelectionModel().getSelectedItem(),
|
getSkinnable().search(gameVersionField.getSelectionModel().getSelectedItem(),
|
||||||
Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem())
|
Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem())
|
||||||
.map(CategoryIndented::getCategory)
|
.map(CategoryIndented::getCategory)
|
||||||
@@ -360,60 +360,93 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
sortComboBox.getSelectionModel().getSelectedItem());
|
sortComboBox.getSelectionModel().getSelectedItem());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
control.listenerHolder.add(FXUtils.observeWeak(
|
||||||
|
() -> filterID.set(filterID.get() + 1),
|
||||||
|
|
||||||
|
control.downloadSource,
|
||||||
|
gameVersionField.getSelectionModel().selectedItemProperty(),
|
||||||
|
categoryComboBox.getSelectionModel().selectedItemProperty(),
|
||||||
|
nameField.textProperty(),
|
||||||
|
sortComboBox.getSelectionModel().selectedItemProperty()
|
||||||
|
));
|
||||||
|
|
||||||
HBox actionsBox = new HBox(8);
|
HBox actionsBox = new HBox(8);
|
||||||
GridPane.setColumnSpan(actionsBox, 4);
|
GridPane.setColumnSpan(actionsBox, 4);
|
||||||
actionsBox.setAlignment(Pos.CENTER);
|
actionsBox.setAlignment(Pos.CENTER);
|
||||||
{
|
{
|
||||||
AggregatedObservableList<Node> actions = new AggregatedObservableList<>();
|
AggregatedObservableList<Node> actions = new AggregatedObservableList<>();
|
||||||
|
|
||||||
|
Holder<Runnable> changeButton = new Holder<>();
|
||||||
|
|
||||||
JFXButton firstPageButton = FXUtils.newBorderButton(i18n("search.first_page"));
|
JFXButton firstPageButton = FXUtils.newBorderButton(i18n("search.first_page"));
|
||||||
firstPageButton.setOnAction(event -> {
|
firstPageButton.setOnAction(event -> {
|
||||||
control.pageOffset.set(0);
|
control.pageOffset.set(0);
|
||||||
|
changeButton.value.run();
|
||||||
searchAction.handle(event);
|
searchAction.handle(event);
|
||||||
});
|
});
|
||||||
firstPageButton.setDisable(true);
|
|
||||||
control.pageCount.addListener((observable, oldValue, newValue) -> firstPageButton.setDisable(control.pageCount.get() == -1));
|
|
||||||
|
|
||||||
JFXButton previousPageButton = FXUtils.newBorderButton(i18n("search.previous_page"));
|
JFXButton previousPageButton = FXUtils.newBorderButton(i18n("search.previous_page"));
|
||||||
previousPageButton.setOnAction(event -> {
|
previousPageButton.setOnAction(event -> {
|
||||||
if (control.pageOffset.get() > 0) {
|
int pageOffset = control.pageOffset.get();
|
||||||
control.pageOffset.set(control.pageOffset.get() - 1);
|
if (pageOffset > 0) {
|
||||||
|
control.pageOffset.set(pageOffset - 1);
|
||||||
|
changeButton.value.run();
|
||||||
searchAction.handle(event);
|
searchAction.handle(event);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
previousPageButton.setDisable(true);
|
|
||||||
control.pageOffset.addListener((observable, oldValue, newValue) -> previousPageButton.setDisable(
|
|
||||||
control.pageCount.get() == -1 || control.pageOffset.get() == 0
|
|
||||||
));
|
|
||||||
|
|
||||||
Label pageOffset = new Label(i18n("search.page_n", 0, "-"));
|
Label pageDescription = new Label();
|
||||||
control.pageOffset.addListener((observable, oldValue, newValue) -> pageOffset.setText(i18n(
|
pageDescription.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||||
"search.page_n", control.pageOffset.get() + 1, control.pageCount.get() == -1 ? "-" : control.pageCount.getValue().toString()
|
int pageCount = control.pageCount.get();
|
||||||
)));
|
return i18n("search.page_n", control.pageOffset.get() + 1, pageCount == -1 ? "-" : String.valueOf(pageCount));
|
||||||
control.pageCount.addListener((observable, oldValue, newValue) -> pageOffset.setText(i18n(
|
}, control.pageOffset, control.pageCount));
|
||||||
"search.page_n", control.pageOffset.get() + 1, control.pageCount.get() == -1 ? "-" : control.pageCount.getValue().toString()
|
|
||||||
)));
|
|
||||||
|
|
||||||
JFXButton nextPageButton = FXUtils.newBorderButton(i18n("search.next_page"));
|
JFXButton nextPageButton = FXUtils.newBorderButton(i18n("search.next_page"));
|
||||||
nextPageButton.setOnAction(event -> {
|
nextPageButton.setOnAction(event -> {
|
||||||
control.pageOffset.set(control.pageOffset.get() + 1);
|
int nv = control.pageOffset.get() + 1;
|
||||||
searchAction.handle(event);
|
if (nv < control.pageCount.get()) {
|
||||||
|
control.pageOffset.set(nv);
|
||||||
|
changeButton.value.run();
|
||||||
|
searchAction.handle(event);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
nextPageButton.setDisable(true);
|
|
||||||
control.pageOffset.addListener((observable, oldValue, newValue) -> nextPageButton.setDisable(
|
|
||||||
control.pageCount.get() == -1 || control.pageOffset.get() >= control.pageCount.get() - 1
|
|
||||||
));
|
|
||||||
control.pageCount.addListener((observable, oldValue, newValue) -> nextPageButton.setDisable(
|
|
||||||
control.pageCount.get() == -1 || control.pageOffset.get() >= control.pageCount.get() - 1
|
|
||||||
));
|
|
||||||
|
|
||||||
JFXButton lastPageButton = FXUtils.newBorderButton(i18n("search.last_page"));
|
JFXButton lastPageButton = FXUtils.newBorderButton(i18n("search.last_page"));
|
||||||
lastPageButton.setOnAction(event -> {
|
lastPageButton.setOnAction(event -> {
|
||||||
control.pageOffset.set(control.pageCount.get() - 1);
|
control.pageOffset.set(control.pageCount.get() - 1);
|
||||||
|
changeButton.value.run();
|
||||||
searchAction.handle(event);
|
searchAction.handle(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
firstPageButton.setDisable(true);
|
||||||
|
previousPageButton.setDisable(true);
|
||||||
lastPageButton.setDisable(true);
|
lastPageButton.setDisable(true);
|
||||||
control.pageCount.addListener((observable, oldValue, newValue) -> lastPageButton.setDisable(control.pageCount.get() == -1 || control.pageOffset.get() >= control.pageCount.get() - 1));
|
nextPageButton.setDisable(true);
|
||||||
|
|
||||||
|
changeButton.value = () -> {
|
||||||
|
int pageOffset = control.pageOffset.get();
|
||||||
|
int pageCount = control.pageCount.get();
|
||||||
|
|
||||||
|
boolean disablePrevious = pageOffset == 0;
|
||||||
|
firstPageButton.setDisable(disablePrevious);
|
||||||
|
previousPageButton.setDisable(disablePrevious);
|
||||||
|
|
||||||
|
boolean disableNext = pageOffset == pageCount - 1;
|
||||||
|
nextPageButton.setDisable(disableNext);
|
||||||
|
lastPageButton.setDisable(disableNext || pageCount == -1);
|
||||||
|
};
|
||||||
|
|
||||||
|
FXUtils.onChange(control.pageCount, pageCountN -> {
|
||||||
|
int pageCount = pageCountN.intValue();
|
||||||
|
|
||||||
|
if (pageCount != -1) {
|
||||||
|
if (control.pageOffset.get() + 1 >= pageCount) {
|
||||||
|
control.pageOffset.set(pageCount - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeButton.value.run();
|
||||||
|
});
|
||||||
|
|
||||||
Pane placeholder = new Pane();
|
Pane placeholder = new Pane();
|
||||||
HBox.setHgrow(placeholder, Priority.SOMETIMES);
|
HBox.setHgrow(placeholder, Priority.SOMETIMES);
|
||||||
@@ -421,13 +454,14 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
JFXButton searchButton = FXUtils.newRaisedButton(i18n("search"));
|
JFXButton searchButton = FXUtils.newRaisedButton(i18n("search"));
|
||||||
searchButton.setOnAction(searchAction);
|
searchButton.setOnAction(searchAction);
|
||||||
|
|
||||||
actions.appendList(FXCollections.observableArrayList(firstPageButton, previousPageButton, pageOffset, nextPageButton, lastPageButton, placeholder, searchButton));
|
actions.appendList(FXCollections.observableArrayList(firstPageButton, previousPageButton, pageDescription, nextPageButton, lastPageButton, placeholder, searchButton));
|
||||||
actions.appendList(control.actions);
|
actions.appendList(control.actions);
|
||||||
Bindings.bindContent(actionsBox.getChildren(), actions.getAggregatedList());
|
Bindings.bindContent(actionsBox.getChildren(), actions.getAggregatedList());
|
||||||
}
|
}
|
||||||
|
|
||||||
searchPane.addRow(rowIndex++, actionsBox);
|
searchPane.addRow(rowIndex++, actionsBox);
|
||||||
|
|
||||||
|
FXUtils.onChange(control.downloadSource, v -> searchAction.handle(null));
|
||||||
nameField.setOnAction(searchAction);
|
nameField.setOnAction(searchAction);
|
||||||
gameVersionField.setOnAction(searchAction);
|
gameVersionField.setOnAction(searchAction);
|
||||||
categoryComboBox.setOnAction(searchAction);
|
categoryComboBox.setOnAction(searchAction);
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||||||
return "asc";
|
return "asc";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int calculateTotalPages(Response<List<CurseAddon>> response, int pageSize) {
|
||||||
|
return (int) Math.ceil((double) Math.min(response.pagination.totalCount, 10000) / pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException {
|
public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException {
|
||||||
int categoryId = 0;
|
int categoryId = 0;
|
||||||
@@ -112,7 +116,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||||||
.getJson(new TypeToken<Response<List<CurseAddon>>>() {
|
.getJson(new TypeToken<Response<List<CurseAddon>>>() {
|
||||||
}.getType());
|
}.getType());
|
||||||
if (searchFilter.isEmpty()) {
|
if (searchFilter.isEmpty()) {
|
||||||
return new SearchResult(response.getData().stream().map(CurseAddon::toMod), (int)Math.ceil((double)response.pagination.totalCount / pageSize));
|
return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/HMCL-dev/HMCL/issues/1549
|
// https://github.com/HMCL-dev/HMCL/issues/1549
|
||||||
@@ -135,7 +139,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
return pair(remoteMod, diff);
|
return pair(remoteMod, diff);
|
||||||
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(CurseAddon::toMod), response.pagination.totalCount);
|
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
Response<ProjectSearchResult> response = HttpRequest.GET(NetworkUtils.withQuery(PREFIX + "/v2/search", query))
|
Response<ProjectSearchResult> response = HttpRequest.GET(NetworkUtils.withQuery(PREFIX + "/v2/search", query))
|
||||||
.getJson(new TypeToken<Response<ProjectSearchResult>>() {
|
.getJson(new TypeToken<Response<ProjectSearchResult>>() {
|
||||||
}.getType());
|
}.getType());
|
||||||
return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int)Math.ceil((double)response.totalHits / pageSize));
|
return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int) Math.ceil((double) response.totalHits / pageSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user