重构 WorldListPage 以使用 ListCell 展示元素#5224
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors WorldListPage to use JavaFX's standard ListCell pattern for displaying world items, eliminating the need for custom WorldListItem and WorldListItemSkin classes.
Changes:
- Replaced custom
WorldListItemControl class with direct use ofWorlddomain objects in the list - Migrated UI rendering logic from
WorldListItemSkininto a newWorldListCellinner class that properly implements theListCellpattern - Moved world operation methods (export, delete, copy, reveal, launch, etc.) from
WorldListItemtoWorldListPageas they now operate directly onWorldobjects
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| WorldListPage.java | Changed generic type from WorldListItem to World, added new WorldListCell inner class implementing ListCell<World>, moved world operation methods from WorldListItem to this class, and added createListCell() override in WorldListPageSkin |
| WorldListItem.java | Deleted - functionality moved to WorldListPage and WorldListCell |
| WorldListItemSkin.java | Deleted - UI rendering logic moved to WorldListCell.updateItem() method |
💡 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 3 out of 3 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| public void delete(World world) { | ||
| WorldManageUIUtils.delete(world, () -> this.getItems().remove(world)); |
There was a problem hiding this comment.
The delete method removes the world from the displayed items list but does not update the worlds field. This creates an inconsistency: if the user toggles the "show all" filter or triggers a refresh, the deleted world may reappear briefly until the refresh completes. Consider also removing the world from the worlds list or calling refresh() after deletion instead of just removing from the items.
| WorldManageUIUtils.delete(world, () -> this.getItems().remove(world)); | |
| WorldManageUIUtils.delete(world, this::refresh); |
| Task.runAsync(() -> world.install(savesDir, name)) | ||
| .whenComplete(Schedulers.javafx(), () -> { | ||
| itemsProperty().add(new WorldListItem(this, new World(savesDir.resolve(name)), backupsDir, profile, id)); | ||
| itemsProperty().add(new World(savesDir.resolve(name))); |
There was a problem hiding this comment.
When a world is successfully installed, it's added directly to the items list but not to the worlds field. This creates inconsistency similar to the delete method: toggling filters or other operations that rely on the worlds field won't include the newly added world until a full refresh occurs. Consider calling refresh() after successful installation or updating both the worlds field and the items list.
| itemsProperty().add(new World(savesDir.resolve(name))); | |
| itemsProperty().add(new World(savesDir.resolve(name))); | |
| // Ensure backing 'worlds' list and filters are updated as well | |
| refresh(); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| resolve.run(); | ||
| refresh(); |
There was a problem hiding this comment.
The refresh() is called after resolve.run() instead of before it, which could lead to incorrect UI state. The resolve.run() callback likely dismisses the prompt dialog, and calling refresh() after it means the list might update after the dialog has already closed. This ordering could cause the new world to not appear in the list until a manual refresh. Consider moving refresh() call before resolve.run().
| resolve.run(); | |
| refresh(); | |
| refresh(); | |
| resolve.run(); |
| } | ||
| }).start(); | ||
| Task.supplyAsync(Schedulers.io(), () -> { | ||
| // Ensure the game version number is parsed |
There was a problem hiding this comment.
The comment states 'Ensure the game version number is parsed' but the return value is ignored. This comment should clarify why we're calling this method for its side effects (likely to cache the version) rather than using the result.
| // Ensure the game version number is parsed | |
| // Preload and cache the game version number for this profile. | |
| // The result is intentionally ignored; parsing may trigger repository-side caching. |
| return stream.toList(); | ||
| } | ||
| }).whenComplete(Schedulers.javafx(), (result, exception) -> { | ||
| if (refreshCount != currentRefresh) { |
There was a problem hiding this comment.
The refreshCount comparison uses != instead of <, which could theoretically fail if refreshCount wraps around after Integer.MAX_VALUE refreshes. While unlikely in practice, using < would be more robust: if (currentRefresh < refreshCount).
| if (refreshCount != currentRefresh) { | |
| if (currentRefresh < refreshCount) { |
| } else { | ||
| imageView.setImage(world.getIcon() == null ? FXUtils.newBuiltinImage("/assets/img/unknown_server.png") : world.getIcon()); | ||
| leftTooltip.setText(world.getFile().toString()); | ||
| content.setTitle(world.getWorldName() != null ? parseColorEscapes(world.getWorldName()) : ""); |
There was a problem hiding this comment.
Setting an empty string when world.getWorldName() is null is inconsistent with the logic in the 'if (empty || world == null)' branch at line 311 which also sets title to "". Consider using a more descriptive placeholder or consistent null handling pattern.
| content.setTitle(world.getWorldName() != null ? parseColorEscapes(world.getWorldName()) : ""); | |
| String worldName = world.getWorldName(); | |
| if (worldName == null || worldName.isEmpty()) { | |
| Path worldFile = world.getFile(); | |
| worldName = worldFile != null && worldFile.getFileName() != null ? worldFile.getFileName().toString() : ""; | |
| } | |
| content.setTitle(worldName.isEmpty() ? "" : parseColorEscapes(worldName)); |
No description provided.