diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java index 2f1ea2aa13..c524fa0df7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/SVG.java @@ -50,6 +50,8 @@ public enum SVG { CHECKROOM("M3 20Q2.575 20 2.2875 19.7125T2 19Q2 18.75 2.1 18.5375T2.4 18.2L11 11.75V10Q11 9.575 11.3 9.2875T12.025 9Q12.65 9 13.075 8.55T13.5 7.475Q13.5 6.85 13.0625 6.425T12 6Q11.375 6 10.9375 6.4375T10.5 7.5H8.5Q8.5 6.05 9.525 5.025T12 4Q13.45 4 14.475 5.0125T15.5 7.475Q15.5 8.65 14.8125 9.575T13 10.85V11.75L21.6 18.2Q21.8 18.325 21.9 18.5375T22 19Q22 19.425 21.7125 19.7125T21 20H3ZM6 18H18L12 13.5 6 18Z"), CHECK_CIRCLE("M10.6 16.6 17.65 9.55 16.25 8.15 10.6 13.8 7.75 10.95 6.35 12.35 10.6 16.6ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"), CLOSE("M6.4 19 5 17.6 10.6 12 5 6.4 6.4 5 12 10.6 17.6 5 19 6.4 13.4 12 19 17.6 17.6 19 12 13.4 6.4 19Z"), + CODE_BLOCKS("M9.6 15.6 11 14.175 8.825 12 11 9.825 9.6 8.4 6 12l3.6 3.6Zm4.8 0L18 12l-3.6-3.6-1.4 1.425L15.175 12 13.0 14.175l1.4 1.425ZM5 21q-0.825 0-1.413-0.587Q3 19.825 3 19V5q0-0.825 0.587-1.413Q4.175 3 5 3h14q0.825 0 1.413 0.587Q21 4.175 21 5v14q0 0.825-0.587 1.413Q19.825 21 19 21H5Zm0-2h14V5H5v14Z"), + CODE_BLOCKS_FILL("M9.6 15.6 11 14.175 8.825 12 11 9.825 9.6 8.4 6 12l3.6 3.6Zm4.8 0L18 12 14.4 8.4 13 9.825 15.175 12 13 14.175 14.4 15.6ZM5 21q-0.825 0-1.413-0.587T3 19V5q0-0.825 0.587-1.413T5 3h14q0.825 0 1.413 0.587T21 5v14q0 0.825-0.587 1.413T19 21H5Z"), CONTENT_COPY("M9 18Q8.175 18 7.5875 17.4125T7 16V4Q7 3.175 7.5875 2.5875T9 2H18Q18.825 2 19.4125 2.5875T20 4V16Q20 16.825 19.4125 17.4125T18 18H9ZM9 16H18V4H9V16ZM5 22Q4.175 22 3.5875 21.4125T3 20V6H5V20H16V22H5ZM9 16V4 16Z"), CREATE_NEW_FOLDER("M14 16h2V14h2V12H16V10H14v2H12v2h2v2ZM4 20q-.825 0-1.4125-.5875T2 18V6q0-.825.5875-1.4125T4 4h6l2 2h8q.825 0 1.4125.5875T22 8V18q0 .825-.5875 1.4125T20 20H4Zm0-2H20V8H11.175l-2-2H4V18ZV6 18Z"), DELETE("M7 21Q6.175 21 5.5875 20.4125T5 19V6H4V4H9V3H15V4H20V6H19V19Q19 19.825 18.4125 20.4125T17 21H7ZM17 6H7V19H17V6ZM9 17H11V8H9V17ZM13 17H15V8H13V17ZM7 6V19 6Z"), diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java index 5d071d4531..f3c1a53fee 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/DownloadPage.java @@ -20,9 +20,12 @@ import com.jfoenix.controls.JFXButton; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.scene.Node; import org.jackhuang.hmcl.download.*; import org.jackhuang.hmcl.download.game.GameRemoteVersion; +import org.jackhuang.hmcl.game.World; import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository; @@ -39,6 +42,7 @@ import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.construct.AdvancedListBox; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; +import org.jackhuang.hmcl.ui.construct.PromptDialogPane; import org.jackhuang.hmcl.ui.construct.TabHeader; import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage; import org.jackhuang.hmcl.ui.decorator.DecoratorPage; @@ -70,6 +74,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage private final TabHeader.Tab modpackTab = new TabHeader.Tab<>("modpackTab"); private final TabHeader.Tab resourcePackTab = new TabHeader.Tab<>("resourcePackTab"); private final TabHeader.Tab shaderTab = new TabHeader.Tab<>("shaderTab"); + private final TabHeader.Tab dataPackTab = new TabHeader.Tab<>("dataPackTab"); private final TabHeader.Tab worldTab = new TabHeader.Tab<>("worldTab"); private final TransitionPane transitionPane = new TransitionPane(); private final DownloadNavigator versionPageNavigator = new DownloadNavigator(); @@ -97,8 +102,9 @@ public DownloadPage(String uploadVersion) { modTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofMod((profile, version, file) -> download(profile, version, file, "mods"), true))); resourcePackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofResourcePack((profile, version, file) -> download(profile, version, file, "resourcepacks"), true))); shaderTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(ModrinthRemoteModRepository.SHADER_PACKS, (profile, version, file) -> download(profile, version, file, "shaderpacks"), true))); + dataPackTab.setNodeSupplier(loadVersionFor(() -> HMCLLocalizedDownloadListPage.ofDataPack((profile, version, file) -> downloadForDataPack(profile, version, file, "datapacks"), true))); worldTab.setNodeSupplier(loadVersionFor(() -> new DownloadListPage(CurseForgeRemoteModRepository.WORLDS))); - tab = new TabHeader(transitionPane, newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, worldTab); + tab = new TabHeader(transitionPane, newGameTab, modpackTab, modTab, resourcePackTab, shaderTab, dataPackTab, worldTab); Profiles.registerVersionsListener(this::loadVersions); @@ -112,6 +118,7 @@ public DownloadPage(String uploadVersion) { .addNavigationDrawerTab(tab, modTab, i18n("mods"), SVG.EXTENSION, SVG.EXTENSION_FILL) .addNavigationDrawerTab(tab, resourcePackTab, i18n("resourcepack"), SVG.TEXTURE) .addNavigationDrawerTab(tab, shaderTab, i18n("download.shader"), SVG.WB_SUNNY, SVG.WB_SUNNY_FILL) + .addNavigationDrawerTab(tab, dataPackTab, i18n("datapack"), SVG.CODE_BLOCKS, SVG.CODE_BLOCKS_FILL) .addNavigationDrawerTab(tab, worldTab, i18n("world"), SVG.PUBLIC); FXUtils.setLimitWidth(sideBar, 200); setLeft(sideBar); @@ -162,6 +169,53 @@ public static void download(Profile profile, @Nullable String version, RemoteMod } + public static void downloadForDataPack(Profile profile, @Nullable String version, RemoteMod.Version file, String subdirectoryName) { + if (version == null) version = profile.getSelectedVersion(); + + Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version) : profile.getRepository().getBaseDirectory(); + + ObservableList worlds = FXCollections.observableArrayList(World.getWorlds(runDirectory.resolve("saves")).toList()); + + Controllers.prompt( + new PromptDialogPane.Builder("安装选项", (result, resolve, reject) -> { + String fileName = ((PromptDialogPane.Builder.StringQuestion) result.get(0)).getValue(); + Integer selectWorld = ((PromptDialogPane.Builder.CandidatesQuestion) result.get(1)).getValue(); + + if (selectWorld == null) { + reject.accept(i18n("mods.install.select_world.not_select.hint")); + return; + } + + if (!FileUtils.isNameValid(fileName)) { + reject.accept(i18n("install.new_game.malformed")); + return; + } + + String selectedWorldFolderName = worlds.get(selectWorld).getFileName(); + Path dest = runDirectory.resolve("saves").resolve(selectedWorldFolderName).resolve(subdirectoryName).resolve(fileName); + + Controllers.taskDialog(Task.composeAsync(() -> { + var task = new FileDownloadTask(file.getFile().getUrl(), dest); + task.setName(file.getName()); + return task; + }).whenComplete(Schedulers.javafx(), exception -> { + if (exception != null) { + if (exception instanceof CancellationException) { + Controllers.showToast(i18n("message.cancelled")); + } else { + Controllers.dialog(DownloadProviders.localizeErrorMessage(exception), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR); + } + } else { + Controllers.showToast(i18n("install.success")); + } + }), i18n("message.downloading"), TaskCancellationAction.NORMAL); + resolve.run(); + }) + .addQuestion(new PromptDialogPane.Builder.StringQuestion(i18n("archive.file.name"), file.getFile().getFilename())) + .addQuestion(new PromptDialogPane.Builder.CandidatesQuestion(i18n("mods.install.select_world.title"), worlds.stream().map(World::getWorldName).toArray(String[]::new))) + ); + } + private void loadVersions(Profile profile) { listenerHolder = new WeakListenerHolder(); runInFX(() -> { @@ -179,6 +233,9 @@ private void loadVersions(Profile profile) { if (shaderTab.isInitialized()) { shaderTab.getNode().loadVersion(profile, null); } + if (dataPackTab.isInitialized()) { + dataPackTab.getNode().loadVersion(profile, null); + } if (worldTab.isInitialized()) { worldTab.getNode().loadVersion(profile, null); } @@ -213,6 +270,10 @@ public void showWorldDownloads() { tab.select(worldTab, false); } + public void showDatapackDownloads() { + tab.select(dataPackTab, false); + } + private static final class DownloadNavigator implements Navigation { private final SettingsMap settings = new SettingsMap(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackInfoDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackInfoDialog.java new file mode 100644 index 0000000000..2be61ce9e8 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackInfoDialog.java @@ -0,0 +1,189 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2025 huangyuhui and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.jackhuang.hmcl.ui.versions; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXDialogLayout; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.stage.Stage; +import org.jackhuang.hmcl.mod.RemoteMod; +import org.jackhuang.hmcl.mod.RemoteModRepository; +import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository; +import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository; +import org.jackhuang.hmcl.setting.Profile; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.Controllers; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; +import org.jackhuang.hmcl.ui.construct.JFXHyperlink; +import org.jackhuang.hmcl.ui.construct.TwoLineListItem; +import org.jackhuang.hmcl.util.Pair; +import org.jackhuang.hmcl.util.io.NetworkUtils; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Optional; +import java.util.StringJoiner; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; +import static org.jackhuang.hmcl.util.Lang.mapOf; +import static org.jackhuang.hmcl.util.Pair.pair; +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +final class DataPackInfoDialog extends JFXDialogLayout { + public DataPackInfoDialog(DataPackListPageSkin.DataPackInfoObject dataPackInfoObject, Profile profile, String versionID) { + + Stage stage = Controllers.getStage(); + { + maxWidthProperty().bind(stage.widthProperty().multiply(0.7)); + } + + //heading area + HBox titleContainer = new HBox(); + { + titleContainer.setSpacing(8); + setHeading(titleContainer); + } + TwoLineListItem titleItem; + { + titleItem = new TwoLineListItem(); + { + titleItem.setTitle(dataPackInfoObject.getTitle()); + } + + ImageView imageView = new ImageView(); + { + FXUtils.limitSize(imageView, 40, 40); + dataPackInfoObject.loadIcon(imageView, null); + } + + titleContainer.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), titleItem); + } + + //body area + Label description = new Label(dataPackInfoObject.getSubtitle()); + { + description.setWrapText(true); + FXUtils.copyOnDoubleClick(description); + } + ScrollPane descriptionPane = new ScrollPane(description); + { + FXUtils.smoothScrolling(descriptionPane); + descriptionPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + descriptionPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.AS_NEEDED); + descriptionPane.setFitToWidth(true); + description.heightProperty().addListener((obs, oldVal, newVal) -> { + double maxHeight = stage.getHeight() * 0.5; + double targetHeight = Math.min(newVal.doubleValue(), maxHeight); + descriptionPane.setPrefViewportHeight(targetHeight); + }); + + setBody(descriptionPane); + } + + //action area + JFXHyperlink openInMcModButton = new JFXHyperlink(i18n("mods.mcmod.search")); + { + openInMcModButton.setOnAction(e -> { + fireEvent(new DialogCloseEvent()); + FXUtils.openLink(NetworkUtils.withQuery("https://search.mcmod.cn/s", mapOf( + pair("key", dataPackInfoObject.getTitle()), + pair("site", "all"), + pair("filter", "0") + ))); + }); + } + + AtomicBoolean initWhatNeedRemoteModInfo = new AtomicBoolean(false); + for (Pair item : Arrays.asList( + pair("mods.curseforge", CurseForgeRemoteModRepository.MODS), + pair("mods.modrinth", ModrinthRemoteModRepository.MODS) + )) { + RemoteModRepository repository = item.getValue(); + JFXHyperlink button = new JFXHyperlink(i18n(item.getKey())); + Task.runAsync(() -> { + Optional versionOptional = repository.getRemoteVersionByLocalFile(null, dataPackInfoObject.getPackInfo().getPath()); + versionOptional.ifPresent(version -> { + RemoteMod remoteMod; + try { + remoteMod = repository.getModById(version.getModid()); + } catch (IOException e) { + LOG.warning("Cannot get remote mod of " + version.getModid(), e); + return; + } + + FXUtils.runInFX(() -> { + button.setOnAction(e -> { + fireEvent(new DialogCloseEvent()); + Controllers.navigate(new DownloadPage( + HMCLLocalizedDownloadListPage.ofDataPack(null, false), + remoteMod, + new Profile.ProfileVersion(profile, versionID), + null + )); + }); + button.setDisable(false); + + if (!initWhatNeedRemoteModInfo.getAndSet(true)) { + ModTranslations.Mod modToOpenInMcMod = ModTranslations.getTranslationsByRepositoryType(repository.getType()).getModByCurseForgeId(remoteMod.getSlug()); + if (modToOpenInMcMod != null) { + openInMcModButton.setOnAction(e -> { + fireEvent(new DialogCloseEvent()); + FXUtils.openLink(ModTranslations.MOD.getMcmodUrl(modToOpenInMcMod)); + }); + openInMcModButton.setText(i18n("mods.mcmod.page")); + } else { + openInMcModButton.setOnAction(e -> { + fireEvent(new DialogCloseEvent()); + FXUtils.openLink(NetworkUtils.withQuery("https://search.mcmod.cn/s", mapOf( + pair("key", remoteMod.getTitle()), + pair("site", "all"), + pair("filter", "0") + ))); + }); + } + + StringJoiner joiner = new StringJoiner(" | "); + joiner.add(remoteMod.getTitle()); + joiner.add(version.getVersion()); + titleItem.setSubtitle(joiner.toString()); + } + }); + }); + }).start(); + button.setDisable(true); + getActions().add(button); + } + + JFXButton okButton = new JFXButton(); + { + okButton.getStyleClass().add("dialog-accept"); + okButton.setText(i18n("button.ok")); + okButton.setOnAction(e -> fireEvent(new DialogCloseEvent())); + } + + getActions().addAll(openInMcModButton, okButton); + + onEscPressed(this, okButton::fire); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPage.java similarity index 67% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPage.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPage.java index 6788648038..9403daaf4c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPage.java @@ -20,7 +20,8 @@ import javafx.collections.ObservableList; import javafx.scene.control.Skin; import javafx.stage.FileChooser; -import org.jackhuang.hmcl.mod.Datapack; +import org.jackhuang.hmcl.mod.DataPack; +import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.Controllers; @@ -42,38 +43,50 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public final class DatapackListPage extends ListPageBase { +public final class DataPackListPage extends ListPageBase { private final Path worldDir; - private final Datapack datapack; + private final DataPack dataPack; + private final Profile profile; + private final String versionID; - public DatapackListPage(WorldManagePage worldManagePage) { + public DataPackListPage(WorldManagePage worldManagePage, Profile profile, String versionID) { this.worldDir = worldManagePage.getWorld().getFile(); + this.profile = profile; + this.versionID = versionID; - datapack = new Datapack(worldDir.resolve("datapacks")); - datapack.loadFromDir(); + dataPack = new DataPack(worldDir.resolve("datapacks")); + dataPack.loadFromDir(); - setItems(MappedObservableList.create(datapack.getPacks(), DatapackListPageSkin.DatapackInfoObject::new)); + setItems(MappedObservableList.create(dataPack.getPacks(), DataPackListPageSkin.DataPackInfoObject::new)); FXUtils.applyDragListener(this, it -> Objects.equals("zip", FileUtils.getExtension(it)), - mods -> mods.forEach(this::installSingleDatapack), this::refresh); + mods -> mods.forEach(this::installSingleDataPack), this::refresh); } - private void installSingleDatapack(Path datapack) { + public Profile getProfile() { + return profile; + } + + public String getVersionID() { + return versionID; + } + + private void installSingleDataPack(Path dataPack) { try { - this.datapack.installPack(datapack); + this.dataPack.installPack(dataPack); } catch (IOException | IllegalArgumentException e) { - LOG.warning("Unable to parse datapack file " + datapack, e); + LOG.warning("Unable to parse datapack file " + dataPack, e); } } @Override protected Skin createDefaultSkin() { - return new DatapackListPageSkin(this); + return new DataPackListPageSkin(this); } public void refresh() { setLoading(true); - Task.runAsync(datapack::loadFromDir) + Task.runAsync(dataPack::loadFromDir) .withRunAsync(Schedulers.javafx(), () -> { setLoading(false); }) @@ -87,18 +100,23 @@ public void add() { List res = FileUtils.toPaths(chooser.showOpenMultipleDialog(Controllers.getStage())); if (res != null) { - res.forEach(this::installSingleDatapack); + res.forEach(this::installSingleDataPack); } - datapack.loadFromDir(); + dataPack.loadFromDir(); + } + + public void navigateToDownloadPage() { + Controllers.getDownloadPage().showDatapackDownloads(); + Controllers.navigate(Controllers.getDownloadPage()); } - void removeSelected(ObservableList selectedItems) { + void removeSelected(ObservableList selectedItems) { selectedItems.stream() - .map(DatapackListPageSkin.DatapackInfoObject::getPackInfo) + .map(DataPackListPageSkin.DataPackInfoObject::getPackInfo) .forEach(pack -> { try { - datapack.deletePack(pack); + dataPack.deletePack(pack); } catch (IOException e) { // Fail to remove mods if the game is running or the datapack is absent. LOG.warning("Failed to delete datapack \"" + pack.getId() + "\"", e); @@ -106,23 +124,23 @@ void removeSelected(ObservableList sele }); } - void enableSelected(ObservableList selectedItems) { + void enableSelected(ObservableList selectedItems) { selectedItems.stream() - .map(DatapackListPageSkin.DatapackInfoObject::getPackInfo) + .map(DataPackListPageSkin.DataPackInfoObject::getPackInfo) .forEach(pack -> pack.setActive(true)); } - void disableSelected(ObservableList selectedItems) { + void disableSelected(ObservableList selectedItems) { selectedItems.stream() - .map(DatapackListPageSkin.DatapackInfoObject::getPackInfo) + .map(DataPackListPageSkin.DataPackInfoObject::getPackInfo) .forEach(pack -> pack.setActive(false)); } void openDataPackFolder() { - FXUtils.openFolder(datapack.getPath()); + FXUtils.openFolder(dataPack.getPath()); } - @NotNull Predicate updateSearchPredicate(String queryString) { + @NotNull Predicate updateSearchPredicate(String queryString) { if (queryString.isBlank()) { return dataPack -> true; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPageSkin.java similarity index 84% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPageSkin.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPageSkin.java index 81394bf70c..b1a37458d6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DatapackListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DataPackListPageSkin.java @@ -43,7 +43,7 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.util.Duration; -import org.jackhuang.hmcl.mod.Datapack; +import org.jackhuang.hmcl.mod.DataPack; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; @@ -55,6 +55,7 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane; import org.jackhuang.hmcl.ui.construct.TwoLineListItem; import org.jackhuang.hmcl.util.Holder; +import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jetbrains.annotations.Nullable; @@ -64,6 +65,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.stream.IntStream; @@ -72,7 +74,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.logging.Logger.LOG; -final class DatapackListPageSkin extends SkinBase { +final class DataPackListPageSkin extends SkinBase { private final TransitionPane toolbarPane; private final HBox searchBar; @@ -80,17 +82,18 @@ final class DatapackListPageSkin extends SkinBase { private final HBox selectingToolbar; InvalidationListener updateBarByStateWeakListener; - private final JFXListView listView; - private final FilteredList filteredList; + private final JFXListView listView; + private final FilteredList filteredList; private final BooleanProperty isSearching = new SimpleBooleanProperty(false); private final BooleanProperty isSelecting = new SimpleBooleanProperty(false); private final JFXTextField searchField; private static final AtomicInteger lastShiftClickIndex = new AtomicInteger(-1); + private static final AtomicBoolean questForContentMenu = new AtomicBoolean(false); final Consumer toggleSelect; - DatapackListPageSkin(DatapackListPage skinnable) { + DataPackListPageSkin(DataPackListPage skinnable) { super(skinnable); StackPane pane = new StackPane(); @@ -112,6 +115,7 @@ final class DatapackListPageSkin extends SkinBase { createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), createToolbarButton2(i18n("datapack.add"), SVG.ADD, skinnable::add), createToolbarButton2(i18n("button.reveal_dir"), SVG.FOLDER_OPEN, skinnable::openDataPackFolder), + createToolbarButton2(i18n("download"), SVG.DOWNLOAD, skinnable::navigateToDownloadPage), createToolbarButton2(i18n("search"), SVG.SEARCH, () -> isSearching.set(true)) ); @@ -181,7 +185,7 @@ final class DatapackListPageSkin extends SkinBase { center.loadingProperty().bind(skinnable.loadingProperty()); Holder lastCell = new Holder<>(); - listView.setCellFactory(x -> new DatapackInfoListCell(listView, lastCell)); + listView.setCellFactory(x -> new DataPackInfoListCell(listView, lastCell)); listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); this.listView.setItems(filteredList); @@ -216,13 +220,13 @@ private void changeToolbar(HBox newToolbar) { } } - static class DatapackInfoObject extends RecursiveTreeObject { + static class DataPackInfoObject extends RecursiveTreeObject { private final BooleanProperty activeProperty; - private final Datapack.Pack packInfo; + private final DataPack.Pack packInfo; private SoftReference> iconCache; - DatapackInfoObject(Datapack.Pack packInfo) { + DataPackInfoObject(DataPack.Pack packInfo) { this.packInfo = packInfo; this.activeProperty = packInfo.activeProperty(); } @@ -235,7 +239,7 @@ String getSubtitle() { return packInfo.getDescription().toString(); } - Datapack.Pack getPackInfo() { + DataPack.Pack getPackInfo() { return packInfo; } @@ -270,7 +274,7 @@ Image loadIcon() { } } - public void loadIcon(ImageView imageView, @Nullable WeakReference> current) { + public void loadIcon(ImageView imageView, @Nullable WeakReference> current) { SoftReference> iconCache = this.iconCache; CompletableFuture imageFuture; if (iconCache != null && (imageFuture = iconCache.get()) != null) { @@ -286,7 +290,7 @@ public void loadIcon(ImageView imageView, @Nullable WeakReference { if (current != null) { - ObjectProperty infoObjectProperty = current.get(); + ObjectProperty infoObjectProperty = current.get(); if (infoObjectProperty == null || infoObjectProperty.get() != this) { // The current ListCell has already switched to another object return; @@ -298,13 +302,15 @@ public void loadIcon(ImageView imageView, @Nullable WeakReference { + private final class DataPackInfoListCell extends MDListCell { final JFXCheckBox checkBox = new JFXCheckBox(); ImageView imageView = new ImageView(); final TwoLineListItem content = new TwoLineListItem(); + JFXButton revealButton = new JFXButton(); + JFXButton infoButton = new JFXButton(); BooleanProperty booleanProperty; - DatapackInfoListCell(JFXListView listView, Holder lastCell) { + DataPackInfoListCell(JFXListView listView, Holder lastCell) { super(listView, lastCell); HBox container = new HBox(8); @@ -319,33 +325,49 @@ private final class DatapackInfoListCell extends MDListCell imageView.setPreserveRatio(true); imageView.setImage(FXUtils.newBuiltinImage("/assets/img/unknown_pack.png")); + revealButton.getStyleClass().add("toggle-icon4"); + revealButton.setGraphic(FXUtils.limitingSize(SVG.FOLDER.createIcon(24), 24, 24)); + infoButton.getStyleClass().add("toggle-icon4"); + infoButton.setGraphic(FXUtils.limitingSize(SVG.INFO.createIcon(24), 24, 24)); + StackPane.setMargin(container, new Insets(8)); - container.getChildren().setAll(checkBox, imageView, content); + container.getChildren().setAll(checkBox, imageView, content, revealButton, infoButton); getContainer().getChildren().setAll(container); getContainer().getParent().addEventHandler(MouseEvent.MOUSE_PRESSED, mouseEvent -> handleSelect(this, mouseEvent)); + getContainer().getParent().addEventHandler(MouseEvent.MOUSE_RELEASED, mouseEvent -> handleSelectOnMouseReleased(this, mouseEvent)); } @Override - protected void updateControl(DatapackInfoObject dataItem, boolean empty) { + protected void updateControl(DataPackInfoObject dataItem, boolean empty) { if (empty) return; content.setTitle(dataItem.getTitle()); - content.setSubtitle(dataItem.getSubtitle()); + String subtitle = dataItem.getSubtitle(); + if (subtitle.contains("\n")) { + subtitle = StringUtils.substringBefore(subtitle, "\n") + "..."; + } + content.setSubtitle(subtitle); if (booleanProperty != null) { checkBox.selectedProperty().unbindBidirectional(booleanProperty); } checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.activeProperty); dataItem.loadIcon(imageView, new WeakReference<>(this.itemProperty())); + revealButton.setOnAction(e -> FXUtils.showFileInExplorer(dataItem.getPackInfo().getPath())); + infoButton.setOnAction(e -> Controllers.dialog(new DataPackInfoDialog(dataItem, getSkinnable().getProfile(), getSkinnable().getVersionID()))); + + } } - public void handleSelect(DatapackInfoListCell cell, MouseEvent mouseEvent) { + public void handleSelect(DataPackInfoListCell cell, MouseEvent mouseEvent) { if (cell.isEmpty()) { mouseEvent.consume(); return; } - if (mouseEvent.isShiftDown()) { + if (mouseEvent.isSecondaryButtonDown()) { + questForContentMenu.set(true); + } else if (mouseEvent.isShiftDown()) { int currentIndex = cell.getIndex(); if (lastShiftClickIndex.get() == -1) { lastShiftClickIndex.set(currentIndex); @@ -371,4 +393,11 @@ public void handleSelect(DatapackInfoListCell cell, MouseEvent mouseEvent) { cell.requestFocus(); mouseEvent.consume(); } + + public void handleSelectOnMouseReleased(DataPackInfoListCell cell, MouseEvent mouseEvent) { + if (questForContentMenu.get()) { + questForContentMenu.set(false); + Controllers.dialog(new DataPackInfoDialog(cell.getItem(), getSkinnable().getProfile(), getSkinnable().getVersionID())); + } + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index db518f3896..2e6a1f75b7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -37,6 +37,7 @@ import org.jackhuang.hmcl.mod.ModLoaderType; import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; +import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.Schedulers; @@ -95,7 +96,11 @@ private void loadModVersions() { Task.supplyAsync(() -> { Stream versions = addon.getData().loadVersions(repository); - return sortVersions(versions); + Stream versionFiltered = versions; + if (page.repository instanceof HMCLLocalizedDownloadListPage.Repository repository && repository.getBackedRemoteModRepository() instanceof ModrinthRemoteModRepository modRepository && modRepository.getProjectType().equals("datapack")) { + versionFiltered = versions.filter((version) -> version.getLoaders().contains(ModLoaderType.DATA_PACK)); + } + return sortVersions(versionFiltered); }).whenComplete(Schedulers.javafx(), (result, exception) -> { if (exception == null) { this.versions = result; @@ -407,24 +412,12 @@ private static final class ModItem extends StackPane { for (ModLoaderType modLoaderType : dataItem.getLoaders()) { switch (modLoaderType) { - case FORGE: - content.addTag(i18n("install.installer.forge")); - break; - case CLEANROOM: - content.addTag(i18n("install.installer.cleanroom")); - break; - case NEO_FORGED: - content.addTag(i18n("install.installer.neoforge")); - break; - case FABRIC: - content.addTag(i18n("install.installer.fabric")); - break; - case LITE_LOADER: - content.addTag(i18n("install.installer.liteloader")); - break; - case QUILT: - content.addTag(i18n("install.installer.quilt")); - break; + case FORGE -> content.addTag(i18n("install.installer.forge")); + case CLEANROOM -> content.addTag(i18n("install.installer.cleanroom")); + case NEO_FORGED -> content.addTag(i18n("install.installer.neoforge")); + case FABRIC -> content.addTag(i18n("install.installer.fabric")); + case LITE_LOADER -> content.addTag(i18n("install.installer.liteloader")); + case QUILT -> content.addTag(i18n("install.installer.quilt")); } } @@ -447,22 +440,13 @@ private static final class ModVersion extends JFXDialogLayout { public ModVersion(RemoteMod.Version version, DownloadPage selfPage) { RemoteModRepository.Type type = selfPage.repository.getType(); - String title; - switch (type) { - case WORLD: - title = "world.download.title"; - break; - case MODPACK: - title = "modpack.download.title"; - break; - case RESOURCE_PACK: - title = "resourcepack.download.title"; - break; - case MOD: - default: - title = "mods.download.title"; - break; - } + String title = switch (type) { + case WORLD -> "world.download.title"; + case MODPACK -> "modpack.download.title"; + case RESOURCE_PACK -> "resourcepack.download.title"; + case DATA_PACK -> "datapack.download.title"; + default -> "mods.download.title"; + }; this.setHeading(new HBox(new Label(i18n(title, version.getName())))); VBox box = new VBox(8); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java index 145878608c..8b5f0b2f36 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/HMCLLocalizedDownloadListPage.java @@ -41,6 +41,10 @@ public static DownloadListPage ofModrinthMod(DownloadPage.DownloadCallback callb return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.MOD, null, ModrinthRemoteModRepository.MODS); } + public static DownloadListPage ofDataPack(DownloadPage.DownloadCallback callback, boolean versionSelection) { + return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.DATA_PACK, CurseForgeRemoteModRepository.DATA_PACKS, ModrinthRemoteModRepository.DATA_PACKS); + } + public static DownloadListPage ofModPack(DownloadPage.DownloadCallback callback, boolean versionSelection) { return new HMCLLocalizedDownloadListPage(callback, versionSelection, RemoteModRepository.Type.MODPACK, CurseForgeRemoteModRepository.MODPACKS, ModrinthRemoteModRepository.MODPACKS); } @@ -65,7 +69,7 @@ private HMCLLocalizedDownloadListPage(DownloadPage.DownloadCallback callback, bo } } - private class Repository extends LocalizedRemoteModRepository { + class Repository extends LocalizedRemoteModRepository { private final RemoteModRepository.Type type; private final CurseForgeRemoteModRepository curseForge; private final ModrinthRemoteModRepository modrinth; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java index 3729e8f8a3..06a5e6353f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java @@ -56,7 +56,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco private final TabHeader header; private final TabHeader.Tab worldInfoTab = new TabHeader.Tab<>("worldInfoPage"); private final TabHeader.Tab worldBackupsTab = new TabHeader.Tab<>("worldBackupsPage"); - private final TabHeader.Tab datapackTab = new TabHeader.Tab<>("datapackListPage"); + private final TabHeader.Tab dataPackTab = new TabHeader.Tab<>("dataPackListPage"); private final TransitionPane transitionPane = new TransitionPane(); @@ -65,13 +65,12 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco public WorldManagePage(World world, Path backupsDir, Profile profile, String id) { this.world = world; this.backupsDir = backupsDir; - this.profile = profile; this.id = id; this.worldInfoTab.setNodeSupplier(() -> new WorldInfoPage(this)); this.worldBackupsTab.setNodeSupplier(() -> new WorldBackupsPage(this)); - this.datapackTab.setNodeSupplier(() -> new DatapackListPage(this)); + this.dataPackTab.setNodeSupplier(() -> new DataPackListPage(this, profile, id)); this.state = new SimpleObjectProperty<>(State.fromTitle(i18n("world.manage.title", world.getWorldName()))); this.header = new TabHeader(transitionPane, worldInfoTab, worldBackupsTab); @@ -97,8 +96,8 @@ public WorldManagePage(World world, Path backupsDir, Profile profile, String id) if (world.getGameVersion() != null && // old game will not write game version to level.dat world.getGameVersion().isAtLeast("1.13", "17w43a")) { - header.getTabs().add(datapackTab); - sideBar.addNavigationDrawerTab(header, datapackTab, i18n("world.datapack"), SVG.EXTENSION, SVG.EXTENSION_FILL); + header.getTabs().add(dataPackTab); + sideBar.addNavigationDrawerTab(header, dataPackTab, i18n("world.datapack"), SVG.EXTENSION, SVG.EXTENSION_FILL); } left.setTop(sideBar); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 9e6fe93c37..e52d618537 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -331,6 +331,17 @@ curse.category.4550=Quests curse.category.4555=World Gen curse.category.4552=Scripts +# datapack. The link above other categories seems to be unavailable (at least I cannot access it). +curse.category.6946=Mod Support +curse.category.6947=Miscellaneous +curse.category.6948=Adventure +curse.category.6949=Fantasy +curse.category.6950=Library +curse.category.6951=Tech +curse.category.6952=Magic +curse.category.6953=Utility +curse.category.8938=ModJam 2025 + curse.sort.author=Author curse.sort.date_created=Date Created curse.sort.last_updated=Last Updated @@ -341,7 +352,7 @@ curse.sort.total_downloads=Total Downloads datetime.format=MMM d, yyyy, h\:mm\:ss a download=Download -download.hint=Install games and modpacks or download mods, resource packs, shaders, and worlds. +download.hint=Install games and modpacks or download mods, resource packs, shaders, datapacks, and worlds. download.code.404=File "%s" not found on the remote server. download.content=Addons download.shader=Shaders @@ -730,6 +741,7 @@ install.installer.not_installed=Not installed install.installer.optifine=OptiFine install.installer.quilt=Quilt install.installer.quilt-api=QSL/QFAPI +install.installer.datapack=Datapack install.installer.version=%s install.installer.external_version=%s (Installed by external process, which cannot be configured) install.installing=Installing @@ -1097,6 +1109,8 @@ mods.url=Official Page mods.update_modpack_mod.warning=Updating mods in a modpack can lead to irreparable results, possibly corrupting the modpack so that it cannot launch. Are you sure you want to update? mods.warning.loader_mismatch=Mod loader mismatch mods.install=Install +mods.install.select_world.title=Select The World To Install Into +mods.install.select_world.not_select.hint=please select a world mods.save_as=Save As mods.unknown=Unknown Mod @@ -1108,6 +1122,7 @@ nbt.title=View File - %s datapack=Datapacks datapack.add=Install Datapack datapack.choose_datapack=Choose datapack to import +datapack.download.title=Download Datapack - %1s datapack.extension=Datapack datapack.title=World [%s] - Datapacks diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index d73afd8f6f..a8aa9b68fa 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -333,6 +333,17 @@ curse.category.4550=任務 curse.category.4555=世界生成 curse.category.4552=指令碼 +# datapack. The link above other categories seems to be unavailable (at least I cannot access it). +curse.category.6946=模組支援 +curse.category.6947=雜項 +curse.category.6948=冒險 +curse.category.6949=奇幻 +curse.category.6950=前置庫 +curse.category.6951=科技 +curse.category.6952=魔法 +curse.category.6953=實用工具 +curse.category.8938=ModJam 2025 + curse.sort.author=作者 curse.sort.date_created=建立日期 curse.sort.last_updated=最近更新 @@ -343,7 +354,7 @@ curse.sort.total_downloads=下載量 datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss download=下載 -download.hint=安裝遊戲和模組包或下載模組、資源包、光影和世界 +download.hint=安裝遊戲和模組包或下載模組、資源包、光影、資料包和世界 download.code.404=遠端伺服器沒有需要下載的檔案:%s download.content=遊戲內容 download.shader=光影 @@ -537,6 +548,7 @@ install.installer.not_installed=未安裝 install.installer.optifine=OptiFine install.installer.quilt=Quilt install.installer.quilt-api=QSL/QFAPI +install.installer.datapack=資料包 install.installer.version=%s install.installer.external_version=%s [由外部安裝的版本,無法解除安裝或更換] install.installing=安裝 @@ -894,6 +906,8 @@ mods.url=官方頁面 mods.update_modpack_mod.warning=更新模組包中的模組可能導致模組包損壞,使模組包無法正常啟動。該操作不可逆,確定要更新嗎? mods.warning.loader_mismatch=模組載入器不匹配 mods.install=安裝到目前實例 +mods.install.select_world.title=選擇要安裝至的世界 +mods.install.select_world.not_select.hint=請選擇一個世界 mods.save_as=下載到本機目錄 mods.unknown=未知模組 @@ -905,6 +919,7 @@ nbt.title=查看檔案 - %s datapack=資料包 datapack.add=新增資料包 datapack.choose_datapack=選取要匯入的資料包壓縮檔 +datapack.download.title=資料包下載 - %1s datapack.extension=資料包 datapack.title=世界 [%s] - 資料包 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 99d93cc883..5126182f4d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -341,6 +341,17 @@ curse.category.4550=任务 curse.category.4555=世界生成 curse.category.4552=脚本 +# datapack. The link above other categories seems to be unavailable (at least I cannot access it). +curse.category.6946=模组支持 +curse.category.6947=杂项 +curse.category.6948=冒险 +curse.category.6949=奇幻 +curse.category.6950=前置库 +curse.category.6951=科技 +curse.category.6952=魔法 +curse.category.6953=实用工具 +curse.category.8938=ModJam 2025 + curse.sort.author=作者 curse.sort.date_created=创建日期 curse.sort.last_updated=最近更新 @@ -351,7 +362,7 @@ curse.sort.total_downloads=下载量 datetime.format=yyyy 年 MM 月 dd 日 HH:mm:ss download=下载 -download.hint=安装游戏和整合包或下载模组、资源包、光影和世界 +download.hint=安装游戏和整合包或下载模组、资源包、光影、数据包和世界 download.code.404=远程服务器不包含需要下载的文件: %s\n你可以点击右上角帮助按钮进行求助。 download.content=游戏内容 download.shader=光影 @@ -547,6 +558,7 @@ install.installer.not_installed=未安装 install.installer.optifine=OptiFine install.installer.quilt=Quilt install.installer.quilt-api=QSL/QFAPI +install.installer.datapack=数据包 install.installer.version=%s install.installer.external_version=%s (由外部安装的版本,无法卸载或更换) install.installing=安装 @@ -904,6 +916,8 @@ mods.url=官方页面 mods.update_modpack_mod.warning=更新整合包中的模组可能导致整合包损坏,使整合包无法正常启动。该操作不可逆,确定要更新吗? mods.warning.loader_mismatch=模组加载器不匹配 mods.install=安装到当前实例 +mods.install.select_world.title=选择要安装到的世界 +mods.install.select_world.not_select.hint=请选择一个世界 mods.save_as=下载到本地文件夹 mods.unknown=未知模组 @@ -915,6 +929,7 @@ nbt.title=查看文件 - %s datapack=数据包 datapack.add=添加数据包 datapack.choose_datapack=选择要导入的数据包压缩包 +datapack.download.title=数据包下载 - %1s datapack.extension=数据包 datapack.title=世界 [%s] - 数据包 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/DataPack.java similarity index 97% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/mod/DataPack.java index 48081fe486..2d8e3108e4 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/DataPack.java @@ -38,14 +38,14 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class Datapack { +public class DataPack { private static final String DISABLED_EXT = "disabled"; private static final String ZIP_EXT = "zip"; private final Path path; private final ObservableList packs = FXCollections.observableArrayList(); - public Datapack(Path path) { + public DataPack(Path path) { this.path = path; } @@ -235,14 +235,14 @@ public static class Pack { private final BooleanProperty activeProperty; private final String id; private final LocalModFile.Description description; - private final Datapack parentDatapack; + private final DataPack parentDataPack; - public Pack(Path path, boolean isDirectory, String id, LocalModFile.Description description, Datapack parentDatapack) { + public Pack(Path path, boolean isDirectory, String id, LocalModFile.Description description, DataPack parentDataPack) { this.path = path; this.isDirectory = isDirectory; this.id = id; this.description = description; - this.parentDatapack = parentDatapack; + this.parentDataPack = parentDataPack; this.statusFile = initializeStatusFile(path, isDirectory); this.activeProperty = initializeActiveProperty(); @@ -300,8 +300,8 @@ public LocalModFile.Description getDescription() { return description; } - public Datapack getParentDatapack() { - return parentDatapack; + public DataPack getParentDatapack() { + return parentDataPack; } public BooleanProperty activeProperty() { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java index f4b8feacd8..65ea195e7c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModLoaderType.java @@ -25,5 +25,5 @@ public enum ModLoaderType { FABRIC, QUILT, LITE_LOADER, - PACK; + DATA_PACK; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java index 5296be3afc..c0dc05527e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/ModManager.java @@ -51,7 +51,7 @@ private interface ModMetadataReader { pair(ForgeOldModMetadata::fromFile, ModLoaderType.FORGE), pair(FabricModMetadata::fromFile, ModLoaderType.FABRIC), pair(QuiltModMetadata::fromFile, ModLoaderType.QUILT), - pair(PackMcMeta::fromFile, ModLoaderType.PACK) + pair(PackMcMeta::fromFile, ModLoaderType.DATA_PACK) ); map.put("zip", zipReaders); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java index 5f74ba7d49..7741674da5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java @@ -33,6 +33,7 @@ enum Type { MODPACK, RESOURCE_PACK, WORLD, + DATA_PACK, CUSTOMIZATION } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java index 4b518c061d..171f198ddb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java @@ -242,6 +242,7 @@ private List reorganizeCategories(List public static final int SECTION_BUKKIT_PLUGIN = 5; public static final int SECTION_MOD = 6; public static final int SECTION_RESOURCE_PACK = 12; + public static final int SECTION_DATA_PACK = 6945; public static final int SECTION_WORLD = 17; public static final int SECTION_MODPACK = 4471; public static final int SECTION_CUSTOMIZATION = 4546; @@ -253,6 +254,7 @@ private List reorganizeCategories(List public static final CurseForgeRemoteModRepository MODS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.MOD, SECTION_MOD); public static final CurseForgeRemoteModRepository MODPACKS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.MODPACK, SECTION_MODPACK); public static final CurseForgeRemoteModRepository RESOURCE_PACKS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.RESOURCE_PACK, SECTION_RESOURCE_PACK); + public static final CurseForgeRemoteModRepository DATA_PACKS = new CurseForgeRemoteModRepository(Type.DATA_PACK, SECTION_DATA_PACK); public static final CurseForgeRemoteModRepository WORLDS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.WORLD, SECTION_WORLD); public static final CurseForgeRemoteModRepository CUSTOMIZATIONS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.CUSTOMIZATION, SECTION_CUSTOMIZATION); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java index 80b89d2599..d15109223d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/PackMcMeta.java @@ -98,7 +98,7 @@ public static PackVersion fromJson(JsonElement element) throws JsonParseExceptio } else if (jsonArray.size() == 2 && jsonArray.get(0) instanceof JsonPrimitive && jsonArray.get(1) instanceof JsonPrimitive) { return new PackVersion(jsonArray.get(0).getAsInt(), jsonArray.get(1).getAsInt()); } else { - LOG.warning("Datapack version array must have 1 or 2 elements, but got " + jsonArray.size()); + LOG.warning("DataPack version array must have 1 or 2 elements, but got " + jsonArray.size()); } } } catch (NumberFormatException e) { @@ -190,7 +190,7 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSys PackMcMeta metadata = JsonUtils.fromNonNullJson(Files.readString(mcmod), PackMcMeta.class); return new LocalModFile( modManager, - modManager.getLocalMod(FileUtils.getNameWithoutExtension(modFile), ModLoaderType.PACK), + modManager.getLocalMod(FileUtils.getNameWithoutExtension(modFile), ModLoaderType.DATA_PACK), modFile, FileUtils.getNameWithoutExtension(modFile), metadata.pack.description, diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java index 4b60bf3cdd..98b8974e51 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java @@ -48,6 +48,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository { public static final ModrinthRemoteModRepository MODPACKS = new ModrinthRemoteModRepository("modpack"); public static final ModrinthRemoteModRepository RESOURCE_PACKS = new ModrinthRemoteModRepository("resourcepack"); public static final ModrinthRemoteModRepository SHADER_PACKS = new ModrinthRemoteModRepository("shader"); + public static final ModrinthRemoteModRepository DATA_PACKS = new ModrinthRemoteModRepository("datapack"); private static final String PREFIX = "https://api.modrinth.com"; @@ -62,6 +63,10 @@ public Type getType() { return Type.MOD; } + public String getProjectType() { + return projectType; + } + private static String convertSortType(SortType sortType) { switch (sortType) { case DATE_CREATED: @@ -521,6 +526,7 @@ public Optional toVersion() { else if ("neoforge".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.NEO_FORGED); else if ("quilt".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.QUILT); else if ("liteloader".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.LITE_LOADER); + else if ("datapack".equalsIgnoreCase(loader)) return Stream.of(ModLoaderType.DATA_PACK); else return Stream.empty(); }).collect(Collectors.toList()) ));