mirror of
https://github.com/SKCraft/Launcher.git
synced 2024-11-30 13:13:58 +01:00
Flesh out curse mod manager
This commit is contained in:
parent
60adb4fde5
commit
66b7b44ac8
@ -1,5 +1,49 @@
|
||||
package com.skcraft.plugin.curse;
|
||||
|
||||
public class CurseApi {
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.skcraft.launcher.util.HttpRequest;
|
||||
import com.skcraft.plugin.curse.model.CurseProject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
|
||||
import static com.skcraft.launcher.util.HttpRequest.url;
|
||||
|
||||
public class CurseApi {
|
||||
private static final String CURSE_ADDON_SEARCH = "https://addons-ecs.forgesvc.net/api/v2/addon/search?";
|
||||
private static final String MINECRAFT_GAME_ID = "432";
|
||||
private static final String CURSE_MOD_SECTION_ID = "6";
|
||||
|
||||
public static List<CurseProject> searchForProjects(String query, String gameVersion) throws IOException, InterruptedException {
|
||||
HttpRequest.Form form = HttpRequest.Form.form();
|
||||
form.add("gameID", MINECRAFT_GAME_ID);
|
||||
form.add("sectionId", CURSE_MOD_SECTION_ID); // Filter to mods only
|
||||
form.add("gameVersion", gameVersion);
|
||||
form.add("searchFilter", query);
|
||||
|
||||
try {
|
||||
URI uri = new URI(CURSE_ADDON_SEARCH + form.toString());
|
||||
|
||||
return HttpRequest.get(uri.toURL())
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.returnContent()
|
||||
.asJson(new TypeReference<List<CurseProject>>() {});
|
||||
} catch (URISyntaxException | MalformedURLException e) {
|
||||
// Shhhh.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static CurseProject getById(String projectId) throws IOException, InterruptedException {
|
||||
String addonUrl = String.format("https://addons-ecs.forgesvc.net/api/v2/addon/%s", projectId);
|
||||
return HttpRequest.get(url(addonUrl))
|
||||
.execute()
|
||||
.expectResponseCode(200)
|
||||
.returnContent()
|
||||
.asJson(CurseProject.class);
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,13 @@ package com.skcraft.plugin.curse;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.skcraft.launcher.model.modpack.Feature;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class CurseMod {
|
||||
private String projectId;
|
||||
|
@ -1,54 +1,275 @@
|
||||
package com.skcraft.plugin.curse.creator;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.skcraft.concurrency.DefaultProgress;
|
||||
import com.skcraft.launcher.creator.model.creator.Pack;
|
||||
import com.skcraft.launcher.creator.plugin.MenuContext;
|
||||
import com.skcraft.launcher.creator.plugin.PluginMenu;
|
||||
import com.skcraft.launcher.dialog.ProgressDialog;
|
||||
import com.skcraft.launcher.swing.EmptyIcon;
|
||||
import com.skcraft.launcher.swing.LinedBoxPanel;
|
||||
import com.skcraft.plugin.curse.model.CurseProject;
|
||||
import com.skcraft.launcher.swing.SwingHelper;
|
||||
import com.skcraft.launcher.util.SwingExecutor;
|
||||
import com.skcraft.plugin.curse.CurseApi;
|
||||
import com.skcraft.plugin.curse.model.*;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.skcraft.launcher.util.HttpRequest.url;
|
||||
|
||||
@Log
|
||||
public class CurseModsDialog extends JDialog {
|
||||
private final LinedBoxPanel panel = new LinedBoxPanel(false).fullyPadded();
|
||||
private final JList<CurseProject> searchResults = new JList<>();
|
||||
private final JList<CurseProject> selected = new JList<>();
|
||||
private final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, searchResults, selected);
|
||||
private final JButton addMod = new JButton("Add Mod >>");
|
||||
private final JButton removeMod = new JButton("Remove Mod");
|
||||
private final CurseSearchResults searchResults = new CurseSearchResults();
|
||||
private final LoadedModList modList = new LoadedModList();
|
||||
|
||||
private final JPanel panel = new JPanel(new BorderLayout(0, 5));
|
||||
private final JTextField searchBox = new JTextField();
|
||||
private final JButton searchButton = new JButton("Search");
|
||||
private final JList<CurseProject> searchPane = new JList<>(searchResults);
|
||||
private final JList<LoadedMod> selectedPane = new JList<>(modList);
|
||||
private final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(searchPane), new JScrollPane(selectedPane));
|
||||
private final JButton addMod = new JButton("Add Mods >>");
|
||||
private final JButton removeMod = new JButton("Remove Mods");
|
||||
private final JButton done = new JButton("Done");
|
||||
|
||||
private final CurseProjectListRenderer projectRenderer = new CurseProjectListRenderer();
|
||||
|
||||
private final ListeningExecutorService executor;
|
||||
private final ObjectMapper mapper;
|
||||
private final Pack pack;
|
||||
private final File curseModsDir;
|
||||
|
||||
public CurseModsDialog(Window owner, Pack pack) {
|
||||
public CurseModsDialog(Window owner, ListeningExecutorService executor, ObjectMapper mapper, Pack pack, List<LoadedMod> currentMods) {
|
||||
super(owner);
|
||||
this.executor = executor;
|
||||
this.mapper = mapper;
|
||||
this.pack = pack;
|
||||
this.curseModsDir = new File(pack.getDirectory(), "cursemods");
|
||||
|
||||
modList.addAll(currentMods);
|
||||
|
||||
initComponents();
|
||||
setMinimumSize(new Dimension(500, 250));
|
||||
setMinimumSize(new Dimension(500, 450));
|
||||
pack();
|
||||
setLocationRelativeTo(owner);
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
splitPane.setDividerLocation(0.5D);
|
||||
searchPane.setCellRenderer(projectRenderer);
|
||||
searchPane.setLayoutOrientation(JList.VERTICAL);
|
||||
searchPane.setVisibleRowCount(0);
|
||||
searchPane.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
|
||||
selectedPane.setCellRenderer(projectRenderer);
|
||||
selectedPane.setLayoutOrientation(JList.VERTICAL);
|
||||
selectedPane.setVisibleRowCount(0);
|
||||
selectedPane.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||
|
||||
splitPane.setDividerLocation(250);
|
||||
splitPane.setDividerSize(10);
|
||||
|
||||
LinedBoxPanel searchBarPanel = new LinedBoxPanel(true);
|
||||
searchBarPanel.addElement(searchBox);
|
||||
searchBarPanel.addElement(searchButton);
|
||||
searchBarPanel.setAlignmentX(CENTER_ALIGNMENT);
|
||||
|
||||
LinedBoxPanel buttonsPanel = new LinedBoxPanel(true);
|
||||
buttonsPanel.addElement(addMod);
|
||||
buttonsPanel.addGlue();
|
||||
buttonsPanel.addElement(done);
|
||||
buttonsPanel.addGlue();
|
||||
buttonsPanel.addElement(removeMod);
|
||||
buttonsPanel.setAlignmentX(CENTER_ALIGNMENT);
|
||||
|
||||
panel.addElement(splitPane);
|
||||
panel.addElement(buttonsPanel);
|
||||
panel.add(searchBarPanel, BorderLayout.NORTH);
|
||||
panel.add(Box.createVerticalStrut(5));
|
||||
panel.add(splitPane, BorderLayout.CENTER);
|
||||
panel.add(buttonsPanel, BorderLayout.SOUTH);
|
||||
|
||||
add(panel);
|
||||
|
||||
addMod.addActionListener(e -> {
|
||||
searchBox.addActionListener(e -> search());
|
||||
searchButton.addActionListener(e -> search());
|
||||
|
||||
addMod.addActionListener(e -> {
|
||||
for (CurseProject project : searchPane.getSelectedValuesList()) {
|
||||
GameVersionFile forVersion = project.findFileForVersion(pack.getCachedConfig().getGameVersion());
|
||||
|
||||
if (forVersion == null) {
|
||||
SwingHelper.showErrorDialog(
|
||||
getOwner(),
|
||||
String.format("Mod %s isn't available for this version.", project.getName()),
|
||||
"Mod Unavailable"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
LoadedMod loadedMod = project.toLoadedMod(forVersion);
|
||||
modList.add(loadedMod);
|
||||
|
||||
executor.submit(() -> {
|
||||
File target = loadedMod.getDiskLocation(curseModsDir);
|
||||
|
||||
log.info(String.format("Saving mod %s", target.getName()));
|
||||
mapper.writeValue(target, loadedMod.getMod());
|
||||
return null;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
removeMod.addActionListener(e -> {
|
||||
for (LoadedMod mod : selectedPane.getSelectedValuesList()) {
|
||||
modList.remove(mod);
|
||||
|
||||
File target = mod.getDiskLocation(curseModsDir);
|
||||
log.info(String.format("Removing mod %s", target.getName()));
|
||||
if (!target.delete()) {
|
||||
SwingHelper.showErrorDialog(getOwner(), String.format("Failed to delete %s", target), "I/O error");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
done.addActionListener(e -> dispose());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
// Let's make sure the image cache is emptied
|
||||
projectRenderer.clearCache();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private void search() {
|
||||
String query = searchBox.getText();
|
||||
ListenableFuture<List<CurseProject>> future = executor.submit(() ->
|
||||
CurseApi.searchForProjects(query, pack.getCachedConfig().getGameVersion()));
|
||||
|
||||
Futures.addCallback(future, new FutureCallback<List<CurseProject>>() {
|
||||
@Override
|
||||
public void onSuccess(List<CurseProject> result) {
|
||||
projectRenderer.clearCacheIfBig();
|
||||
searchResults.updateResults(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
SwingHelper.showErrorDialog(getOwner(), t.getMessage(), "Search failure", t);
|
||||
}
|
||||
}, SwingExecutor.INSTANCE);
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
private static class ImageWorker extends SwingWorker<BufferedImage, BufferedImage> {
|
||||
private final String imgUrl;
|
||||
private final Consumer<BufferedImage> consumer;
|
||||
|
||||
@Override
|
||||
protected BufferedImage doInBackground() throws Exception {
|
||||
return ImageIO.read(url(imgUrl));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
consumer.accept(this.get());
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
SwingHelper.showErrorDialog(null, e.getMessage(), "Worker error", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void downloadImage(String imageUrl, Consumer<BufferedImage> consumer) {
|
||||
ImageWorker worker = new ImageWorker(imageUrl, consumer);
|
||||
worker.execute();
|
||||
}
|
||||
}
|
||||
|
||||
private static class CurseProjectListRenderer extends JLabel implements ListCellRenderer<ProjectHolder> {
|
||||
private HashMap<String, BufferedImage> cache;
|
||||
|
||||
public CurseProjectListRenderer() {
|
||||
this.cache = new HashMap<>();
|
||||
}
|
||||
|
||||
private BufferedImage getCachedIcon(String imgUrl, Runnable cb) {
|
||||
if (!cache.containsKey(imgUrl)) {
|
||||
ImageWorker.downloadImage(imgUrl, img -> {
|
||||
cache.put(imgUrl, img);
|
||||
cb.run();
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return cache.get(imgUrl);
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
public void clearCacheIfBig() {
|
||||
if (this.cache.size() > 200) {
|
||||
this.cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static Dimension getScaledDimensions(int imageWidth, int imageHeight) {
|
||||
double widthRatio = 32.0D / imageWidth;
|
||||
double heightRatio = 32.0D / imageHeight;
|
||||
double ratio = Math.min(widthRatio, heightRatio);
|
||||
|
||||
int newWidth = (int) Math.floor(imageWidth * ratio);
|
||||
int newHeight = (int) Math.floor(imageHeight * ratio);
|
||||
|
||||
return new Dimension(newWidth, newHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends ProjectHolder> list, ProjectHolder value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
CurseProject project = value.getProject();
|
||||
|
||||
setText(project.getName());
|
||||
setToolTipText(project.getSummary());
|
||||
|
||||
BufferedImage ref = getCachedIcon(project.getDefaultIcon().getThumbnailUrl(), list::repaint);
|
||||
if (ref != null) {
|
||||
Dimension scaled = getScaledDimensions(ref.getWidth(), ref.getHeight());
|
||||
setIcon(new ImageIcon(ref.getScaledInstance(scaled.width, scaled.height, Image.SCALE_SMOOTH)));
|
||||
} else {
|
||||
setIcon(new EmptyIcon(32 ,32));
|
||||
}
|
||||
|
||||
if (isSelected) {
|
||||
setOpaque(true);
|
||||
setBackground(list.getSelectionBackground());
|
||||
setForeground(list.getSelectionForeground());
|
||||
} else {
|
||||
setOpaque(false);
|
||||
setForeground(list.getForeground());
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Menu implements PluginMenu {
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Add Mods";
|
||||
@ -60,9 +281,23 @@ public class CurseModsDialog extends JDialog {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(Window owner, ActionEvent e, Pack pack) {
|
||||
CurseModsDialog dialog = new CurseModsDialog(owner, pack);
|
||||
dialog.setVisible(true);
|
||||
public void onOpen(MenuContext ctx, ActionEvent e, Pack pack) {
|
||||
PackModScanner scanner = new PackModScanner(mapper);
|
||||
|
||||
ListenableFuture<?> future = ctx.getExecutor().submit(() -> {
|
||||
scanner.walk(new File(pack.getDirectory(), "cursemods"));
|
||||
return null;
|
||||
});
|
||||
|
||||
ProgressDialog.showProgress(ctx.getOwner(), future,
|
||||
new DefaultProgress(-1, "Loading mods..."),"Curse", "Contacting Curse API");
|
||||
SwingHelper.addErrorDialogCallback(ctx.getOwner(), future);
|
||||
|
||||
future.addListener(() -> {
|
||||
CurseModsDialog dialog = new CurseModsDialog(ctx.getOwner(), ctx.getExecutor(), mapper, pack, scanner.getResult());
|
||||
dialog.setTitle(getTitle());
|
||||
dialog.setVisible(true);
|
||||
}, SwingExecutor.INSTANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
package com.skcraft.plugin.curse.creator;
|
||||
|
||||
import com.beust.jcommander.internal.Lists;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.skcraft.launcher.builder.DirectoryWalker;
|
||||
import com.skcraft.plugin.curse.CurseApi;
|
||||
import com.skcraft.plugin.curse.CurseMod;
|
||||
import com.skcraft.plugin.curse.model.CurseProject;
|
||||
import com.skcraft.plugin.curse.model.LoadedMod;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class PackModScanner extends DirectoryWalker {
|
||||
private final ObjectMapper mapper;
|
||||
|
||||
@Getter private final List<LoadedMod> result = Lists.newArrayList();
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
protected void onFile(File file, String relPath) throws IOException {
|
||||
CurseMod mod = mapper.readValue(file, CurseMod.class);
|
||||
CurseProject project = CurseApi.getById(mod.getProjectId());
|
||||
|
||||
result.add(new LoadedMod(mod, project));
|
||||
}
|
||||
}
|
@ -1,19 +1,27 @@
|
||||
package com.skcraft.plugin.curse.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.skcraft.plugin.curse.CurseMod;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class CurseProject {
|
||||
public class CurseProject implements ProjectHolder {
|
||||
private String id;
|
||||
private String name;
|
||||
private String websiteUrl;
|
||||
private String summary;
|
||||
private String slug;
|
||||
private List<GameVersionFile> gameVersionLatestFiles;
|
||||
private List<Attachment> attachments;
|
||||
|
||||
@Override
|
||||
public CurseProject getProject() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public GameVersionFile findFileForVersion(String version) {
|
||||
for (GameVersionFile file : gameVersionLatestFiles) {
|
||||
@ -24,4 +32,34 @@ public class CurseProject {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public LoadedMod toLoadedMod(GameVersionFile versionFile) {
|
||||
CurseMod mod = new CurseMod(id, versionFile.getProjectFileId(), null);
|
||||
return new LoadedMod(mod, this);
|
||||
}
|
||||
|
||||
public Attachment getDefaultIcon() {
|
||||
for (Attachment attachment : attachments) {
|
||||
if (attachment.isDefault()) {
|
||||
return attachment;
|
||||
}
|
||||
}
|
||||
|
||||
if (!attachments.isEmpty()) {
|
||||
return attachments.get(0);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class Attachment {
|
||||
private String title;
|
||||
private String thumbnailUrl;
|
||||
private String url;
|
||||
|
||||
@JsonProperty("isDefault")
|
||||
private boolean isDefault;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package com.skcraft.plugin.curse.model;
|
||||
|
||||
import com.beust.jcommander.internal.Lists;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.List;
|
||||
|
||||
public class CurseSearchResults extends AbstractListModel<CurseProject> {
|
||||
private List<CurseProject> results = Lists.newArrayList();
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return results.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CurseProject getElementAt(int index) {
|
||||
return results.get(index);
|
||||
}
|
||||
|
||||
public void updateResults(List<CurseProject> newResults) {
|
||||
int before = results.size();
|
||||
|
||||
results = newResults;
|
||||
this.fireContentsChanged(this, 0, before - 1);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.skcraft.plugin.curse.model;
|
||||
|
||||
import com.skcraft.plugin.curse.CurseMod;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Represents mod metadata cached in-memory for the purpose of rendering the "Add Mods" screen.
|
||||
*/
|
||||
@Data
|
||||
public class LoadedMod implements ProjectHolder {
|
||||
private final CurseMod mod;
|
||||
private final CurseProject project;
|
||||
|
||||
public File getDiskLocation(File curseModsDir) {
|
||||
return new File(curseModsDir, String.format("%s.json", project.getSlug()));
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.skcraft.plugin.curse.model;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class LoadedModList extends AbstractListModel<LoadedMod> {
|
||||
private List<LoadedMod> mods = Lists.newArrayList();
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return mods.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadedMod getElementAt(int index) {
|
||||
return mods.get(index);
|
||||
}
|
||||
|
||||
public void addAll(Collection<? extends LoadedMod> other) {
|
||||
int start = mods.size();
|
||||
mods.addAll(other);
|
||||
fireIntervalAdded(this, start, start + other.size());
|
||||
}
|
||||
|
||||
public void add(LoadedMod mod) {
|
||||
mods.add(mod);
|
||||
|
||||
int idx = mods.size() - 1;
|
||||
fireIntervalAdded(this, idx, idx);
|
||||
}
|
||||
|
||||
public void remove(LoadedMod mod) {
|
||||
int index = mods.indexOf(mod);
|
||||
|
||||
if (index > -1) {
|
||||
mods.remove(index);
|
||||
fireIntervalRemoved(this, index, index);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.skcraft.plugin.curse.model;
|
||||
|
||||
public interface ProjectHolder {
|
||||
CurseProject getProject();
|
||||
}
|
@ -33,6 +33,7 @@ import com.skcraft.launcher.creator.model.creator.*;
|
||||
import com.skcraft.launcher.creator.model.swing.PackTableModel;
|
||||
import com.skcraft.launcher.creator.plugin.CreatorPluginWrapper;
|
||||
import com.skcraft.launcher.creator.plugin.CreatorToolsPlugin;
|
||||
import com.skcraft.launcher.creator.plugin.MenuContext;
|
||||
import com.skcraft.launcher.creator.plugin.PluginMenu;
|
||||
import com.skcraft.launcher.creator.server.TestServer;
|
||||
import com.skcraft.launcher.creator.server.TestServerBuilder;
|
||||
@ -335,7 +336,7 @@ public class PackManagerController {
|
||||
return;
|
||||
}
|
||||
|
||||
menu.onOpen(frame, e, pack.orNull());
|
||||
menu.onOpen(new MenuContext(frame, creator.getExecutor()), e, pack.orNull());
|
||||
});
|
||||
|
||||
submenu.add(item);
|
||||
|
@ -0,0 +1,12 @@
|
||||
package com.skcraft.launcher.creator.plugin;
|
||||
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import lombok.Data;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
@Data
|
||||
public class MenuContext {
|
||||
private final Window owner;
|
||||
private final ListeningExecutorService executor;
|
||||
}
|
@ -2,14 +2,13 @@ package com.skcraft.launcher.creator.plugin;
|
||||
|
||||
import com.skcraft.launcher.creator.model.creator.Pack;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
public interface PluginMenu {
|
||||
String getTitle();
|
||||
|
||||
/**
|
||||
* Return true if {@link PluginMenu#onOpen(Window, ActionEvent, Pack)} should only be called
|
||||
* Return true if {@link PluginMenu#onOpen(MenuContext, ActionEvent, Pack)} should only be called
|
||||
* if the user has selected a pack.
|
||||
* @return True to require a pack, false if you don't need one.
|
||||
*/
|
||||
@ -17,10 +16,9 @@ public interface PluginMenu {
|
||||
|
||||
/**
|
||||
* Called when the menu item was clicked.
|
||||
*
|
||||
* @param owner Window reference for parenting dialogs.
|
||||
* @param ctx Context object with various useful objects.
|
||||
* @param e Action event that triggered this call.
|
||||
* @param pack Pack to operate on; guaranteed to be non-null if {@link PluginMenu#requiresPack()} returns true.
|
||||
*/
|
||||
void onOpen(Window owner, ActionEvent e, Pack pack);
|
||||
void onOpen(MenuContext ctx, ActionEvent e, Pack pack);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user