* 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.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
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.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.AggregatedObservableList;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
@@ -64,10 +64,10 @@ import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
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.util.i18n.I18n.i18n;
|
||||
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 {
|
||||
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 StringProperty downloadSource = new SimpleStringProperty();
|
||||
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||
private TaskExecutor executor;
|
||||
private int searchID = 0;
|
||||
protected RemoteModRepository repository;
|
||||
|
||||
private Runnable retrySearch;
|
||||
@@ -163,11 +163,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
setLoading(true);
|
||||
setFailed(false);
|
||||
|
||||
if (executor != null && !executor.isCancelled()) {
|
||||
executor.cancel();
|
||||
}
|
||||
|
||||
executor = Task.supplyAsync(() -> {
|
||||
int currentSearchID = searchID = searchID + 1;
|
||||
Task.supplyAsync(() -> {
|
||||
Profile.ProfileVersion version = this.version.get();
|
||||
if (StringUtils.isBlank(version.getVersion())) {
|
||||
return userGameVersion;
|
||||
@@ -176,9 +173,11 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
? version.getProfile().getRepository().getGameVersion(version.getVersion()).orElse("")
|
||||
: "";
|
||||
}
|
||||
}).thenApplyAsync(gameVersion ->
|
||||
repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC)
|
||||
).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
}).thenApplyAsync(gameVersion -> repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC)).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (searchID != currentSearchID) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
if (exception == null) {
|
||||
items.setAll(result.getResults().collect(Collectors.toList()));
|
||||
@@ -196,7 +195,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
return i18n("curse.category." + category);
|
||||
}
|
||||
|
||||
protected String getLocalizedCategoryIndent(ModDownloadListPageSkin.CategoryIndented category) {
|
||||
private String getLocalizedCategoryIndent(ModDownloadListPageSkin.CategoryIndented category) {
|
||||
return StringUtils.repeats(' ', category.indent * 4) +
|
||||
(category.getCategory() == null
|
||||
? i18n("curse.category.0")
|
||||
@@ -344,13 +343,14 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
sortComboBox.getSelectionModel().select(0);
|
||||
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 -> {
|
||||
if (!previousSearchFilter.get().equals(nameField.getText())) {
|
||||
if (currentFilterID.get() != filterID.get()) {
|
||||
control.pageOffset.set(0);
|
||||
}
|
||||
currentFilterID.set(filterID.get());
|
||||
|
||||
previousSearchFilter.set(nameField.getText());
|
||||
getSkinnable().search(gameVersionField.getSelectionModel().getSelectedItem(),
|
||||
Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem())
|
||||
.map(CategoryIndented::getCategory)
|
||||
@@ -360,60 +360,93 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
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);
|
||||
GridPane.setColumnSpan(actionsBox, 4);
|
||||
actionsBox.setAlignment(Pos.CENTER);
|
||||
{
|
||||
AggregatedObservableList<Node> actions = new AggregatedObservableList<>();
|
||||
|
||||
Holder<Runnable> changeButton = new Holder<>();
|
||||
|
||||
JFXButton firstPageButton = FXUtils.newBorderButton(i18n("search.first_page"));
|
||||
firstPageButton.setOnAction(event -> {
|
||||
control.pageOffset.set(0);
|
||||
changeButton.value.run();
|
||||
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"));
|
||||
previousPageButton.setOnAction(event -> {
|
||||
if (control.pageOffset.get() > 0) {
|
||||
control.pageOffset.set(control.pageOffset.get() - 1);
|
||||
int pageOffset = control.pageOffset.get();
|
||||
if (pageOffset > 0) {
|
||||
control.pageOffset.set(pageOffset - 1);
|
||||
changeButton.value.run();
|
||||
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, "-"));
|
||||
control.pageOffset.addListener((observable, oldValue, newValue) -> pageOffset.setText(i18n(
|
||||
"search.page_n", control.pageOffset.get() + 1, control.pageCount.get() == -1 ? "-" : control.pageCount.getValue().toString()
|
||||
)));
|
||||
control.pageCount.addListener((observable, oldValue, newValue) -> pageOffset.setText(i18n(
|
||||
"search.page_n", control.pageOffset.get() + 1, control.pageCount.get() == -1 ? "-" : control.pageCount.getValue().toString()
|
||||
)));
|
||||
Label pageDescription = new Label();
|
||||
pageDescription.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||
int pageCount = control.pageCount.get();
|
||||
return i18n("search.page_n", control.pageOffset.get() + 1, pageCount == -1 ? "-" : String.valueOf(pageCount));
|
||||
}, control.pageOffset, control.pageCount));
|
||||
|
||||
JFXButton nextPageButton = FXUtils.newBorderButton(i18n("search.next_page"));
|
||||
nextPageButton.setOnAction(event -> {
|
||||
control.pageOffset.set(control.pageOffset.get() + 1);
|
||||
searchAction.handle(event);
|
||||
int nv = control.pageOffset.get() + 1;
|
||||
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"));
|
||||
lastPageButton.setOnAction(event -> {
|
||||
control.pageOffset.set(control.pageCount.get() - 1);
|
||||
changeButton.value.run();
|
||||
searchAction.handle(event);
|
||||
});
|
||||
|
||||
firstPageButton.setDisable(true);
|
||||
previousPageButton.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();
|
||||
HBox.setHgrow(placeholder, Priority.SOMETIMES);
|
||||
@@ -421,13 +454,14 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
JFXButton searchButton = FXUtils.newRaisedButton(i18n("search"));
|
||||
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);
|
||||
Bindings.bindContent(actionsBox.getChildren(), actions.getAggregatedList());
|
||||
}
|
||||
|
||||
searchPane.addRow(rowIndex++, actionsBox);
|
||||
|
||||
FXUtils.onChange(control.downloadSource, v -> searchAction.handle(null));
|
||||
nameField.setOnAction(searchAction);
|
||||
gameVersionField.setOnAction(searchAction);
|
||||
categoryComboBox.setOnAction(searchAction);
|
||||
|
||||
@@ -94,6 +94,10 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
||||
return "asc";
|
||||
}
|
||||
|
||||
private int calculateTotalPages(Response<List<CurseAddon>> response, int pageSize) {
|
||||
return (int) Math.ceil((double) Math.min(response.pagination.totalCount, 10000) / pageSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException {
|
||||
int categoryId = 0;
|
||||
@@ -112,7 +116,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
||||
.getJson(new TypeToken<Response<List<CurseAddon>>>() {
|
||||
}.getType());
|
||||
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
|
||||
@@ -135,7 +139,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -95,7 +95,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
Response<ProjectSearchResult> response = HttpRequest.GET(NetworkUtils.withQuery(PREFIX + "/v2/search", query))
|
||||
.getJson(new TypeToken<Response<ProjectSearchResult>>() {
|
||||
}.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
|
||||
|
||||
Reference in New Issue
Block a user