Conversation
| LOG.warning("Failed to export logs", e); | ||
| Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(e), null, MessageType.ERROR)); | ||
| Platform.runLater(() -> { | ||
| spinnerPane.showSpinner(); |
|
|
||
| Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.success", outputFile))); | ||
| Platform.runLater(() -> { | ||
| spinnerPane.showSpinner(); |
There was a problem hiding this comment.
Pull request overview
This PR adds loading spinner indicators to two export operations in the HMCL launcher: the game crash info export in GameCrashWindow and the launcher log export in SettingsPage. Both operations involve file compression that can take significant time, and previously had no visual feedback, causing users to think the button click was not registered and leading to repeated exports.
Changes:
- Wraps the export buttons in both
GameCrashWindowandSettingsPagewithSpinnerPanecontrols that show a loading spinner during the export operation. - Shows the spinner before starting the async export task and hides it in all completion paths (success and error).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java |
Adds a SpinnerPane wrapping the crash info export button, shows spinner on click, hides on completion. Adds small-spinner-pane CSS class. |
HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java |
Adds a SpinnerPane wrapping the log export button, shows spinner on click, hides on completion in both success and error paths. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @SuppressWarnings("FieldCanBeLocal") | ||
| private final InvalidationListener updateListener; | ||
|
|
||
| private final SpinnerPane spinnerPane = new SpinnerPane(); |
There was a problem hiding this comment.
The small-spinner-pane style class is missing on the spinnerPane in SettingsPage. Every other usage of SpinnerPane wrapping a button in the codebase adds spinnerPane.getStyleClass().add("small-spinner-pane") to reduce the spinner size (radius 10, stroke 3) to match the button. Without it, the default (larger) spinner will appear when loading, which will look out of place next to the small border button.
See examples: GameCrashWindow.java:119, AccountListItemSkin.java:95,130,165,170, CreateAccountPane.java:135, DialogPane.java:58, InputDialogPane.java:61, MicrosoftAccountLoginPane.java:108, AddAuthlibInjectorServerPane.java:79.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return CompletableFuture.supplyAsync(() -> | ||
| logs.stream().map(Log::getLog).collect(Collectors.joining("\n"))) | ||
| .thenComposeAsync(logs -> { | ||
| long processStartTime = managedProcess.getProcess().info() |
There was a problem hiding this comment.
In exportGameCrashInfo(), the lambda parameter name logs in thenComposeAsync(logs -> { ... }) shadows the logs field. Renaming the parameter (e.g., logsText/logText) would make the code easier to follow and avoid accidental confusion between the list and the joined string.
| SpinnerPane exportLogPane = new SpinnerPane(); | ||
|
|
||
| JFXButton logButton = FXUtils.newBorderButton(i18n("settings.launcher.launcher_log.export")); | ||
| logButton.setOnAction(e -> onExportLogs()); | ||
| exportLogPane.setContent(logButton); | ||
| logButton.setOnAction(e -> { |
There was a problem hiding this comment.
SpinnerPane wrapping a button should include the small-spinner-pane style class; otherwise the spinner tends to be oversized and can shift the layout compared to other spinner-wrapped buttons in the UI (e.g., LogWindow export dump). Add the style class to exportLogPane for consistent sizing.
| private CompletableFuture<Path> onExportLogs() { | ||
| return CompletableFuture.supplyAsync(Lang.wrap(() -> { | ||
| String nameBase = "hmcl-exported-logs-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")); |
There was a problem hiding this comment.
onExportLogs() performs file I/O and compression but uses CompletableFuture.supplyAsync(...) with the default executor (ForkJoin common pool). Use Schedulers.io() (and pass it as the executor argument to supplyAsync) so this work runs on the project’s I/O pool/virtual threads instead of competing with compute tasks.
| private CompletableFuture<Path> exportGameCrashInfo() { | ||
| Path logFile = Paths.get("minecraft-exported-crash-info-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".zip").toAbsolutePath(); | ||
|
|
||
| CompletableFuture.supplyAsync(() -> | ||
| return CompletableFuture.supplyAsync(() -> | ||
| logs.stream().map(Log::getLog).collect(Collectors.joining("\n"))) | ||
| .thenComposeAsync(logs -> { | ||
| long processStartTime = managedProcess.getProcess().info() |
There was a problem hiding this comment.
exportGameCrashInfo() ultimately does log export/compression work, but the initial CompletableFuture.supplyAsync(...) uses the default executor (common pool). Consider switching to Schedulers.io() for these async stages to keep I/O work off the compute pool and take advantage of the project’s I/O executor/virtual threads.
前置:#5693
来自崩溃群
这两个地方涉及到文件压缩,如果待压缩文件比较大耗时会比较久,目前点击没有任何反馈用户可能以为没点到导致重复导出