-
Notifications
You must be signed in to change notification settings - Fork 779
feat: 资源包管理 #4475
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: 资源包管理 #4475
Changes from 4 commits
92d175b
b99520d
3866eb1
69510ac
2f71a16
cad53a1
1d4fe22
0d1c22e
aaab964
553b499
e042b8f
1a56e85
8d5708b
ff58841
7857ad4
416e07f
e5ed6d0
f540134
0aa3097
6028071
45f243d
9f8999d
9506910
2e84869
d6cc5f9
66ff6b4
c9dc5f9
f358c17
d8c589a
67c0ef6
05ed1a8
b84b2ad
0d63ba2
53ffe6f
4a476dc
4288254
3e64bb6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,231 @@ | ||||||||||||
| package org.jackhuang.hmcl.ui.versions; | ||||||||||||
|
|
||||||||||||
| import com.jfoenix.controls.JFXButton; | ||||||||||||
| import javafx.geometry.Insets; | ||||||||||||
| import javafx.geometry.Pos; | ||||||||||||
| import javafx.scene.Node; | ||||||||||||
| import javafx.scene.control.Control; | ||||||||||||
| import javafx.scene.control.Skin; | ||||||||||||
| import javafx.scene.control.SkinBase; | ||||||||||||
| import javafx.scene.image.Image; | ||||||||||||
| import javafx.scene.image.ImageView; | ||||||||||||
| import javafx.scene.layout.BorderPane; | ||||||||||||
| import javafx.scene.layout.HBox; | ||||||||||||
| import javafx.stage.FileChooser; | ||||||||||||
| import org.jackhuang.hmcl.resourcepack.ResourcepackFile; | ||||||||||||
| import org.jackhuang.hmcl.setting.Profile; | ||||||||||||
| import org.jackhuang.hmcl.setting.Theme; | ||||||||||||
| import org.jackhuang.hmcl.task.Schedulers; | ||||||||||||
| import org.jackhuang.hmcl.task.Task; | ||||||||||||
| import org.jackhuang.hmcl.ui.*; | ||||||||||||
| import org.jackhuang.hmcl.ui.construct.MessageDialogPane; | ||||||||||||
| import org.jackhuang.hmcl.ui.construct.RipplerContainer; | ||||||||||||
| import org.jackhuang.hmcl.ui.construct.TwoLineListItem; | ||||||||||||
| import org.jackhuang.hmcl.util.io.FileUtils; | ||||||||||||
|
|
||||||||||||
| import java.io.File; | ||||||||||||
| import java.io.IOException; | ||||||||||||
| import java.io.InputStream; | ||||||||||||
| import java.nio.file.Files; | ||||||||||||
| import java.nio.file.Path; | ||||||||||||
| import java.util.Arrays; | ||||||||||||
| import java.util.Comparator; | ||||||||||||
| import java.util.List; | ||||||||||||
| import java.util.stream.Collectors; | ||||||||||||
| import java.util.stream.Stream; | ||||||||||||
|
|
||||||||||||
| import static org.jackhuang.hmcl.ui.FXUtils.runInFX; | ||||||||||||
| import static org.jackhuang.hmcl.util.i18n.I18n.i18n; | ||||||||||||
| import static org.jackhuang.hmcl.util.logging.Logger.LOG; | ||||||||||||
|
|
||||||||||||
| public class ResourcepackListPage extends ListPageBase<ResourcepackListPage.ResourcepackItem> implements VersionPage.VersionLoadable { | ||||||||||||
| private Path resourcepackDirectory; | ||||||||||||
|
|
||||||||||||
| public ResourcepackListPage() { | ||||||||||||
| FXUtils.applyDragListener(this, file -> file.isFile() && file.getName().endsWith(".zip"), files -> addFiles(files.stream().map(File::toPath).collect(Collectors.toList()))); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private static Node createIcon(Path img) { | ||||||||||||
| ImageView imageView = new ImageView(); | ||||||||||||
| imageView.setFitWidth(32); | ||||||||||||
| imageView.setFitHeight(32); | ||||||||||||
|
|
||||||||||||
| if (Files.exists(img)) { | ||||||||||||
| try (InputStream is = Files.newInputStream(img)) { | ||||||||||||
| Image image = new Image(is); | ||||||||||||
| imageView.setImage(image); | ||||||||||||
| } catch (IOException ignored) { | ||||||||||||
CiiLu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| if (imageView.getImage() == null) { | ||||||||||||
| imageView.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png")); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return imageView; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| @Override | ||||||||||||
| protected Skin<?> createDefaultSkin() { | ||||||||||||
| return new ResourcepackListPageSkin(this); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| @Override | ||||||||||||
| public void loadVersion(Profile profile, String version) { | ||||||||||||
| this.resourcepackDirectory = profile.getRepository().getResourcepacksDirectory(version); | ||||||||||||
|
|
||||||||||||
| try { | ||||||||||||
| if (!Files.exists(resourcepackDirectory)) { | ||||||||||||
| Files.createDirectories(resourcepackDirectory); | ||||||||||||
| } | ||||||||||||
| } catch (IOException e) { | ||||||||||||
| LOG.error("Failed to create resourcepack directory", e); | ||||||||||||
CiiLu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||
| } | ||||||||||||
| refresh(); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| public void refresh() { | ||||||||||||
| Task.runAsync(Schedulers.javafx(), this::load).whenComplete(Schedulers.javafx(), (result, exception) -> setLoading(false)).start(); | ||||||||||||
CiiLu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||
| setLoading(true); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| public void addFiles(List<Path> files) { | ||||||||||||
| if (resourcepackDirectory == null) return; | ||||||||||||
|
|
||||||||||||
| try { | ||||||||||||
| for (Path file : files) { | ||||||||||||
| Path target = resourcepackDirectory.resolve(file.getFileName()); | ||||||||||||
| if (!Files.exists(target)) { | ||||||||||||
| Files.copy(file, target); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
CiiLu marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
| } catch (IOException e) { | ||||||||||||
| Controllers.dialog(i18n("resourcepack.add.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR); | ||||||||||||
| LOG.warning("Failed to add resourcepacks", e); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| public void onAddFiles() { | ||||||||||||
| FileChooser fileChooser = new FileChooser(); | ||||||||||||
| fileChooser.setTitle(i18n("resourcepack.add")); | ||||||||||||
| fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("resourcepack"), "*.zip")); | ||||||||||||
| List<File> files = fileChooser.showOpenMultipleDialog(Controllers.getStage()); | ||||||||||||
| if (files != null && !files.isEmpty()) { | ||||||||||||
| addFiles(files.stream().map(File::toPath).collect(Collectors.toList())); | ||||||||||||
CiiLu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| private void load() { | ||||||||||||
| itemsProperty().clear(); | ||||||||||||
| if (resourcepackDirectory == null || !Files.exists(resourcepackDirectory)) return; | ||||||||||||
|
|
||||||||||||
| try (Stream<Path> stream = Files.list(resourcepackDirectory)) { | ||||||||||||
| stream.forEach(path -> { | ||||||||||||
| try { | ||||||||||||
| itemsProperty().add(new ResourcepackItem(ResourcepackFile.parse(path))); | ||||||||||||
|
||||||||||||
| itemsProperty().add(new ResourcepackItem(ResourcepackFile.parse(path))); | |
| ResourcepackFile resourcepackFile = ResourcepackFile.parse(path); | |
| if (resourcepackFile != null) { | |
| itemsProperty().add(new ResourcepackItem(resourcepackFile)); | |
| } |
CiiLu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
CiiLu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
CiiLu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
Outdated
Copilot
AI
Sep 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove commented-out code blocks. These appear to be related to unused checkbox functionality and should be cleaned up.
Outdated
Copilot
AI
Sep 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove commented-out code. If this padding adjustment was intentional, either apply it or remove the comment entirely.
| // center.setPadding(new Insets(0, 0, 0, 8)); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package org.jackhuang.hmcl.resourcepack; | ||
|
|
||
| import com.google.gson.JsonParser; | ||
|
|
||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
|
|
||
| public interface ResourcepackFile { | ||
| String getDescription(); | ||
|
|
||
| String getName(); | ||
|
|
||
| File getFile(); | ||
|
|
||
| Path getIcon(); | ||
|
|
||
| default String parseDescriptionFromJson(String json) { | ||
| try { | ||
| return JsonParser.parseString(json).getAsJsonObject().getAsJsonObject("pack").get("description").getAsString(); | ||
CiiLu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } catch (Exception ignored) { | ||
| return ""; | ||
| } | ||
|
|
||
| } | ||
|
|
||
| static ResourcepackFile parse(Path path) throws IOException { | ||
| if (Files.isRegularFile(path) && path.toString().toLowerCase().endsWith(".zip")) { | ||
CiiLu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return new ResourcepackZipFile(path.toFile()); | ||
| } else if (Files.isDirectory(path) && Files.exists(path.resolve("pack.mcmeta"))) { | ||
| return new ResourcepackFolder(path.toFile()); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.