Refactor + a few improvements to UI #8
|
@ -79,7 +79,7 @@ public class FrogLoaderImpl implements FrogLoader {
|
|||
LOGGER.info("Launching...");
|
||||
plugins.forEach(FrogPlugin::run);
|
||||
} catch (Throwable t) {
|
||||
LoaderGui.builder().setContent(LoaderGui.ContentType.GENERIC_ERROR, t).addReport(CrashReportGenerator.writeReport(t, collectMods().values())).build().show();
|
||||
LoaderGui.execReport(CrashReportGenerator.writeReport(t, collectMods().values()), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,281 +1,136 @@
|
|||
package dev.frogmc.frogloader.impl.gui;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.plaf.basic.BasicBorders;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import dev.frogmc.frogloader.impl.FrogLoaderImpl;
|
||||
import dev.frogmc.frogloader.impl.gui.page.BreakingDepPage;
|
||||
import dev.frogmc.frogloader.impl.gui.page.ReportPage;
|
||||
import dev.frogmc.frogloader.impl.gui.page.UnfulfilledDepPage;
|
||||
import dev.frogmc.frogloader.impl.mod.ModDependencyResolver;
|
||||
import dev.frogmc.frogloader.impl.mod.ModUtil;
|
||||
import dev.frogmc.frogloader.impl.util.URLUtil;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import dev.frogmc.frogloader.impl.util.PlatformUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class LoaderGui {
|
||||
public class LoaderGui extends JFrame {
|
||||
|
||||
private final JFrame frame;
|
||||
private final boolean keepRunning;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(LoaderGui.class);
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
private final JLabel header;
|
||||
private final JTabbedPane tabbedPane;
|
||||
private final JPanel actions;
|
||||
|
||||
private LoaderGui() {
|
||||
String title = "FrogLoader";
|
||||
String version = LoaderGui.class.getPackage().getImplementationVersion();
|
||||
if (version != null)
|
||||
title += ' ' + version;
|
||||
|
||||
setTitle(title);
|
||||
|
||||
setSize(952, 560);
|
||||
setLocationRelativeTo(null);
|
||||
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
|
||||
this.header = new JLabel();
|
||||
this.header.setFont(this.header.getFont().deriveFont(Font.BOLD, this.header.getFont().getSize() * 2));
|
||||
this.header.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
|
||||
add(this.header, BorderLayout.NORTH);
|
||||
|
||||
this.tabbedPane = new JTabbedPane();
|
||||
add(this.tabbedPane, BorderLayout.CENTER);
|
||||
|
||||
this.actions = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||
|
||||
JButton joinDiscordButton = new JButton("Discord Server");
|
||||
joinDiscordButton.addActionListener(event -> PlatformUtil.open(URI.create("https://discord.frogmc.dev")));
|
||||
this.actions.add(joinDiscordButton);
|
||||
|
||||
JButton exitButton = new JButton("Exit");
|
||||
exitButton.addActionListener(event -> dispose());
|
||||
this.actions.add(exitButton);
|
||||
|
||||
add(this.actions, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
private static JFrame getFrame(Path report) {
|
||||
var frame = new JFrame();
|
||||
frame.setTitle("FrogLoader " + LoaderGui.class.getPackage().getImplementationVersion());
|
||||
frame.setSize(952, 560);
|
||||
frame.setLocationRelativeTo(null);
|
||||
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
public void setHeader(String text) {
|
||||
this.header.setText(text);
|
||||
}
|
||||
|
||||
JPanel actions = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||
if (report != null) {
|
||||
actions.add(new JButton(new AbstractAction("Open Report") {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
URLUtil.open(report.toUri());
|
||||
}
|
||||
}));
|
||||
actions.add(new JButton(new AbstractAction("Copy Report") {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
URLUtil.copyStringContent(report.toUri());
|
||||
}
|
||||
}));
|
||||
public void addTab(String name, Component component) {
|
||||
this.tabbedPane.add(name, component);
|
||||
}
|
||||
|
||||
public void addAction(String label, ActionListener listener) {
|
||||
var button = new JButton(label);
|
||||
button.addActionListener(listener);
|
||||
this.actions.add(button, this.actions.getComponentCount() - 2);
|
||||
}
|
||||
|
||||
public static void execUnfulfilledDep(Path reportPath, ModDependencyResolver.UnfulfilledDependencyException ex, boolean keepRunning) {
|
||||
exec(gui -> {
|
||||
gui.setHeader("Found " + ex.getDependencies().size() + " problems");
|
||||
gui.addTab("Info", new UnfulfilledDepPage(ex));
|
||||
addReport(gui, reportPath);
|
||||
}, keepRunning);
|
||||
}
|
||||
|
||||
public static void execBreakingDep(Path reportPath, ModDependencyResolver.BreakingModException ex, boolean keepRunning) {
|
||||
exec(gui -> {
|
||||
gui.setHeader("Found " + ex.getBreaks().size() + " problems");
|
||||
gui.addTab("Info", new BreakingDepPage(ex));
|
||||
addReport(gui, reportPath);
|
||||
}, keepRunning);
|
||||
}
|
||||
|
||||
public static void execReport(Path reportPath, boolean keepRunning) {
|
||||
exec(gui -> {
|
||||
gui.setHeader("Caught Fatal Error during game startup");
|
||||
addReport(gui, reportPath);
|
||||
}, keepRunning);
|
||||
}
|
||||
|
||||
private static void addReport(LoaderGui gui, Path reportPath) {
|
||||
gui.addAction("Open Report", event -> PlatformUtil.open(reportPath.toUri()));
|
||||
gui.addAction("Copy Report", event -> PlatformUtil.copyStringContent(reportPath));
|
||||
gui.addTab("Report", new ReportPage(reportPath));
|
||||
}
|
||||
|
||||
private static void exec(Consumer<LoaderGui> init, boolean keepRunning) {
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
|
||||
// calling countDown just once will resume execution on this thread
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
EventQueue.invokeLater(() -> {
|
||||
LoaderGui gui = new LoaderGui();
|
||||
|
||||
init.accept(gui);
|
||||
|
||||
gui.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosed(WindowEvent e) {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
gui.setVisible(true);
|
||||
});
|
||||
|
||||
latch.await();
|
||||
} catch (Throwable error) {
|
||||
LOGGER.warn("Error displaying GUI", error);
|
||||
}
|
||||
|
||||
actions.add(new JButton(new AbstractAction("Join Discord") {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
URLUtil.open(URI.create("https://discord.frogmc.dev"));
|
||||
}
|
||||
}));
|
||||
|
||||
actions.add(new JButton(new AbstractAction("Exit") {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
frame.dispose();
|
||||
}
|
||||
}));
|
||||
frame.add(actions, BorderLayout.SOUTH);
|
||||
return frame;
|
||||
}
|
||||
|
||||
private static String printVersionRange(ModDependencyResolver.VersionRange range) {
|
||||
return "range: \n" + range.toString().replace("||", "or");
|
||||
}
|
||||
|
||||
private static JPanel getEntry(String description, ModDependencyResolver.VersionRange range, Color background, @Nullable String icon, List<JButton> actions) {
|
||||
JPanel entry = new JPanel(new BorderLayout());
|
||||
entry.setBorder(BasicBorders.getInternalFrameBorder());
|
||||
Box text = Box.createVerticalBox();
|
||||
text.setBorder(BorderFactory.createEmptyBorder());
|
||||
JTextPane desc = new JTextPane();
|
||||
desc.setContentType("text/html");
|
||||
desc.setEditable(false);
|
||||
desc.setBackground(background);
|
||||
desc.setText("<html>" + description.replace("<", "<").replace("\n", "<br>") + "</html>");
|
||||
JPanel actionPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||
actions.forEach(actionPanel::add);
|
||||
if (icon != null) {
|
||||
URL location = LoaderGui.class.getResource(icon);
|
||||
if (location != null) {
|
||||
entry.add(new JLabel(new ImageIcon(location)), BorderLayout.WEST);
|
||||
}
|
||||
}
|
||||
text.add(desc);
|
||||
JCheckBox showAdvanced = new JCheckBox("Show raw version range");
|
||||
JTextPane advanced = new JTextPane();
|
||||
advanced.setEditable(false);
|
||||
advanced.setBackground(background);
|
||||
advanced.setText(range.toString());
|
||||
advanced.setVisible(false);
|
||||
text.add(advanced);
|
||||
showAdvanced.addActionListener(new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
advanced.setVisible(showAdvanced.isSelected());
|
||||
}
|
||||
});
|
||||
entry.add(text);
|
||||
Box bottom = Box.createHorizontalBox();
|
||||
bottom.add(showAdvanced, actionPanel);
|
||||
entry.add(bottom, BorderLayout.SOUTH);
|
||||
return entry;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BusyWait")
|
||||
public void show() {
|
||||
frame.setVisible(true);
|
||||
|
||||
while (frame.isVisible()) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!keepRunning) {
|
||||
if (!keepRunning)
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private Path report;
|
||||
private Consumer<JFrame> contentFunc;
|
||||
private boolean keepRunning = false;
|
||||
|
||||
public <T> Builder setContent(ContentType<T> type, T argument) {
|
||||
contentFunc = f -> type.contentSetter.accept(f, argument);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addReport(Path report) {
|
||||
this.report = report;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder keepRunning() {
|
||||
keepRunning = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LoaderGui build() {
|
||||
JFrame frame = getFrame(report);
|
||||
if (contentFunc != null) {
|
||||
contentFunc.accept(frame);
|
||||
}
|
||||
return new LoaderGui(frame, keepRunning);
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
public static class ContentType<T> {
|
||||
public static final ContentType<ModDependencyResolver.UnfulfilledDependencyException> INFO_UNFULFILLED_DEP = new ContentType<>((frame, ex) -> {
|
||||
JPanel pane = new JPanel(new BorderLayout());
|
||||
JTextPane title = new JTextPane();
|
||||
title.setBackground(pane.getBackground());
|
||||
title.setEditable(false);
|
||||
int size = ex.getDependencies().size();
|
||||
title.setText("Found " + size + " Error" + (size > 1 ? "s:" : ":"));
|
||||
title.setFont(title.getFont().deriveFont(Font.BOLD, 16f));
|
||||
title.setBorder(BorderFactory.createEmptyBorder(8, 0, 8, 0));
|
||||
pane.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
|
||||
pane.add(title, BorderLayout.NORTH);
|
||||
|
||||
Box list = Box.createVerticalBox();
|
||||
ex.getDependencies().forEach(e -> {
|
||||
StringBuilder description = new StringBuilder();
|
||||
if (e.presentVersion() != null) {
|
||||
description.append("Mod ").append(e.source().id()).append(" (").append(e.source().name()).append(") depends on ");
|
||||
if (e.dependencyName() != null) {
|
||||
description.append(e.dependency()).append(" (").append(e.dependencyName()).append(") ");
|
||||
} else {
|
||||
description.append("a Mod with id ").append(e.dependency());
|
||||
}
|
||||
description.append(" with a version matching ").append(printVersionRange(e.range())).append(", but a different version is present or provided: ").append(e.presentVersion());
|
||||
} else {
|
||||
description.append("Mod ").append(e.source().id()).append(" (").append(e.source().name()).append(") depends on ");
|
||||
if (e.dependencyName() != null) {
|
||||
description.append(e.dependency()).append(" (").append(e.dependencyName()).append(") ");
|
||||
} else {
|
||||
description.append("a Mod with id ").append(e.dependency());
|
||||
}
|
||||
description.append(" with a version matching ").append(printVersionRange(e.range())).append(". \nNo version is currently available.");
|
||||
}
|
||||
description.append("\nSuggested Solution: Install ")
|
||||
.append(e.range().maxCompatible().or(e.range()::minCompatible).map(Objects::toString)
|
||||
.map(s -> "0.0.0".equals(s) ? "any version" : "version " + s).orElse("<unknown>"))
|
||||
.append(" of ");
|
||||
if (e.dependencyName() != null) {
|
||||
description.append(e.dependency()).append(" (").append(e.dependencyName()).append(") ");
|
||||
} else {
|
||||
description.append("Mod with id ").append(e.dependency());
|
||||
}
|
||||
List<JButton> actions = new ArrayList<>();
|
||||
if (e.link() != null) {
|
||||
boolean install = e.link().endsWith(FrogLoaderImpl.MOD_FILE_EXTENSION);
|
||||
String name = install ? "Install" : "Open mod page";
|
||||
JButton urlButton = new JButton(new AbstractAction(name) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent event) {
|
||||
if (install) {
|
||||
ModUtil.installMod(e.link());
|
||||
} else {
|
||||
URLUtil.open(URI.create(e.link()));
|
||||
}
|
||||
}
|
||||
});
|
||||
actions.add(urlButton);
|
||||
}
|
||||
JPanel entry = getEntry(description.toString(), e.range(), list.getBackground(), e.source().icon(), actions);
|
||||
list.add(entry);
|
||||
});
|
||||
pane.add(new JScrollPane(list));
|
||||
frame.add(pane);
|
||||
});
|
||||
public static final ContentType<ModDependencyResolver.BreakingModException> INFO_BREAKING_DEP = new ContentType<>((frame, ex) -> {
|
||||
JPanel pane = new JPanel(new BorderLayout());
|
||||
JTextPane title = new JTextPane();
|
||||
title.setBackground(pane.getBackground());
|
||||
title.setEditable(false);
|
||||
int size = ex.getBreaks().size();
|
||||
title.setText("Found " + size + " Error" + (size > 1 ? "s:" : ":"));
|
||||
title.setFont(title.getFont().deriveFont(Font.BOLD, 16f));
|
||||
pane.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
|
||||
pane.add(title, BorderLayout.NORTH);
|
||||
|
||||
Box list = Box.createVerticalBox();
|
||||
ex.getBreaks().forEach(e -> {
|
||||
String description = "Mod %s (%s) breaks with mod %s (%s) for versions matching %s \n(present: %s)".formatted(e.source().id(), e.source().name(), e.broken().id(), e.broken().name(), printVersionRange(e.range()), e.broken().version()) +
|
||||
"\nSuggested Solution: Install " +
|
||||
e.range().maxCompatible().or(e.range()::minCompatible).map(Objects::toString)
|
||||
.map(s -> "0.0.0".equals(s) ? "any version" : "version " + s).orElse("<unknown>") +
|
||||
" of Mod %s (%s) ".formatted(e.broken().id(), e.broken().name());
|
||||
List<JButton> actions = new ArrayList<>();
|
||||
|
||||
JPanel entry = getEntry(description, e.range(), list.getBackground(), e.source().icon(), actions);
|
||||
entry.setSize(list.getWidth(), entry.getHeight());
|
||||
list.add(entry);
|
||||
});
|
||||
pane.add(new JScrollPane(list));
|
||||
frame.add(pane);
|
||||
});
|
||||
public static final ContentType<Throwable> GENERIC_ERROR = new ContentType<>((frame, throwable) -> {
|
||||
JPanel pane = new JPanel(new BorderLayout());
|
||||
|
||||
JTextPane title = new JTextPane();
|
||||
title.setBackground(pane.getBackground());
|
||||
title.setEditable(false);
|
||||
title.setText("Caught Fatal Error during game startup:");
|
||||
title.setFont(title.getFont().deriveFont(Font.BOLD, 16f));
|
||||
pane.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
|
||||
pane.add(title, BorderLayout.NORTH);
|
||||
|
||||
JTextPane error = new JTextPane();
|
||||
error.setEditable(false);
|
||||
StringWriter writer = new StringWriter();
|
||||
throwable.printStackTrace(new PrintWriter(writer));
|
||||
error.setText(writer.toString());
|
||||
|
||||
pane.add(new JScrollPane(error));
|
||||
|
||||
frame.add(pane);
|
||||
});
|
||||
|
||||
private final BiConsumer<JFrame, T> contentSetter;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package dev.frogmc.frogloader.impl.gui.component;
|
||||
|
||||
import dev.frogmc.frogloader.impl.mod.ModDependencyResolver;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.plaf.basic.BasicBorders;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.net.URL;
|
||||
|
||||
public class DependencyErrorEntry extends JPanel {
|
||||
|
||||
private final JPanel actions;
|
||||
|
||||
public DependencyErrorEntry(String description, ModDependencyResolver.VersionRange range, Color background, @Nullable String icon) {
|
||||
super(new BorderLayout());
|
||||
|
||||
setBorder(BasicBorders.getInternalFrameBorder());
|
||||
|
||||
Box text = Box.createVerticalBox();
|
||||
text.setBorder(BorderFactory.createEmptyBorder());
|
||||
|
||||
JTextPane desc = new JTextPane();
|
||||
desc.setContentType("text/html");
|
||||
desc.setEditable(false);
|
||||
desc.setBackground(background);
|
||||
desc.setText("<html>" + description.replace("<", "<").replace("\n", "<br>") + "</html>");
|
||||
text.add(desc);
|
||||
|
||||
add(text, BorderLayout.NORTH);
|
||||
|
||||
this.actions = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
add(this.actions, BorderLayout.SOUTH);
|
||||
|
||||
if (icon != null) {
|
||||
URL location = getClass().getResource(icon);
|
||||
|
||||
if (location != null)
|
||||
add(new JLabel(new ImageIcon(location)), BorderLayout.WEST);
|
||||
}
|
||||
}
|
||||
|
||||
public void addAction(String label, ActionListener listener) {
|
||||
var button = new JButton(label);
|
||||
button.addActionListener(listener);
|
||||
this.actions.add(button);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package dev.frogmc.frogloader.impl.gui.page;
|
||||
|
||||
import dev.frogmc.frogloader.impl.gui.component.DependencyErrorEntry;
|
||||
import dev.frogmc.frogloader.impl.mod.ModDependencyResolver;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.Objects;
|
||||
|
||||
public class BreakingDepPage extends JScrollPane {
|
||||
|
||||
public BreakingDepPage(ModDependencyResolver.BreakingModException ex) {
|
||||
getHorizontalScrollBar().setUnitIncrement(16);
|
||||
getVerticalScrollBar().setUnitIncrement(16);
|
||||
|
||||
Box list = Box.createVerticalBox();
|
||||
|
||||
ex.getBreaks().forEach(entry -> {
|
||||
String description =
|
||||
"""
|
||||
Mod %s (%s) breaks with mod %s (%s) for versions matching range: %s (present: %s)
|
||||
Suggested Solution: Install %s of Mod %s (%s)
|
||||
""";
|
||||
|
||||
description = description.formatted(
|
||||
entry.source().id(),
|
||||
entry.source().name(),
|
||||
entry.broken().id(),
|
||||
entry.broken().name(),
|
||||
entry.range().toString(" or "),
|
||||
entry.broken().version(),
|
||||
entry.range()
|
||||
.maxCompatible()
|
||||
.or(entry.range()::minCompatible)
|
||||
.map(Objects::toString)
|
||||
.map(s -> "0.0.0".equals(s) ? "any version" : "version " + s)
|
||||
.orElse("<unknown>"),
|
||||
entry.broken().id(),
|
||||
entry.broken().name()
|
||||
);
|
||||
|
||||
DependencyErrorEntry result = new DependencyErrorEntry(
|
||||
description,
|
||||
entry.range(),
|
||||
list.getBackground(),
|
||||
entry.source().icon()
|
||||
);
|
||||
list.add(result);
|
||||
});
|
||||
setViewportView(list);
|
||||
SwingUtilities.invokeLater(() -> getViewport().setViewPosition(new Point()));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package dev.frogmc.frogloader.impl.gui.page;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class ReportPage extends JScrollPane {
|
||||
|
||||
public ReportPage(Path reportPath) {
|
||||
getHorizontalScrollBar().setUnitIncrement(16);
|
||||
getVerticalScrollBar().setUnitIncrement(16);
|
||||
|
||||
JTextPane text = new JTextPane();
|
||||
text.setEditable(false);
|
||||
try {
|
||||
text.setText(Files.readString(reportPath, StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
StringWriter writer = new StringWriter();
|
||||
PrintWriter printer = new PrintWriter(writer);
|
||||
printer.printf("Could not load contents of %s:%n", reportPath);
|
||||
e.printStackTrace(printer);
|
||||
}
|
||||
setViewportView(text);
|
||||
SwingUtilities.invokeLater(() -> getViewport().setViewPosition(new Point(0, 0)));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
package dev.frogmc.frogloader.impl.gui.page;
|
||||
|
||||
import dev.frogmc.frogloader.impl.gui.component.DependencyErrorEntry;
|
||||
import dev.frogmc.frogloader.impl.mod.ModDependencyResolver;
|
||||
import dev.frogmc.frogloader.impl.util.PlatformUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Objects;
|
||||
|
||||
public class UnfulfilledDepPage extends JScrollPane {
|
||||
|
||||
public UnfulfilledDepPage(ModDependencyResolver.UnfulfilledDependencyException ex) {
|
||||
getHorizontalScrollBar().setUnitIncrement(16);
|
||||
getVerticalScrollBar().setUnitIncrement(16);
|
||||
|
||||
Box list = Box.createVerticalBox();
|
||||
|
||||
ex.getDependencies().forEach(entry -> {
|
||||
StringBuilder description = new StringBuilder();
|
||||
if (entry.presentVersion() != null) {
|
||||
description.append("Mod ").append(entry.source().id()).append(" (").append(entry.source().name()).append(") depends on ");
|
||||
if (entry.dependencyName() != null) {
|
||||
description.append(entry.dependency()).append(" (").append(entry.dependencyName()).append(") ");
|
||||
} else {
|
||||
description.append("a Mod with id ").append(entry.dependency());
|
||||
}
|
||||
description.append(" with a version matching range:\n").append(entry.range().toString(" or ")).append("\nbut a different version is present or provided: ").append(entry.presentVersion());
|
||||
} else {
|
||||
description.append("Mod ").append(entry.source().id()).append(" (").append(entry.source().name()).append(") depends on ");
|
||||
if (entry.dependencyName() != null) {
|
||||
description.append(entry.dependency()).append(" (").append(entry.dependencyName()).append(") ");
|
||||
} else {
|
||||
description.append("a Mod with id ").append(entry.dependency());
|
||||
}
|
||||
description.append(" with a version matching range:\n").append(entry.range().toString(" or ")).append("\nNo version is currently available.");
|
||||
}
|
||||
description.append("\nSuggested Solution: Install ")
|
||||
.append(
|
||||
entry
|
||||
.range()
|
||||
.maxCompatible()
|
||||
.or(entry.range()::minCompatible)
|
||||
.map(Objects::toString)
|
||||
.map(s -> "0.0.0".equals(s) ? "any version" : "version " + s)
|
||||
.orElse("<unknown>")
|
||||
)
|
||||
.append(" of ");
|
||||
if (entry.dependencyName() != null) {
|
||||
description.append(entry.dependency()).append(" (").append(entry.dependencyName()).append(") ");
|
||||
} else {
|
||||
description.append("Mod with id ").append(entry.dependency());
|
||||
}
|
||||
|
||||
DependencyErrorEntry result = new DependencyErrorEntry(
|
||||
description.toString(),
|
||||
entry.range(),
|
||||
list.getBackground(),
|
||||
entry.source().icon()
|
||||
);
|
||||
|
||||
if (entry.link() != null) {
|
||||
try {
|
||||
URI uri = new URI(entry.link());
|
||||
result.addAction("Open mod page", event -> PlatformUtil.open(uri));
|
||||
} catch (URISyntaxException ignored) {
|
||||
}
|
||||
}
|
||||
list.add(result);
|
||||
});
|
||||
|
||||
setViewportView(list);
|
||||
SwingUtilities.invokeLater(() -> getViewport().setViewPosition(new Point()));
|
||||
}
|
||||
|
||||
}
|
|
@ -153,7 +153,7 @@ public class AccessWidener {
|
|||
public record Entry(AccessType type, String targetType, String className, String name, String descriptor) {
|
||||
|
||||
public Entry(String[] line) {
|
||||
this(AccessType.of(line[0]), line[1], line[2], line[3], line[4]);
|
||||
this(AccessType.of(line[0]), line[1], line[2], line.length > 3 ? line[3] : null, line.length > 4 ? line[4] : null);
|
||||
}
|
||||
|
||||
public boolean isAccessGreaterThan(Entry other) {
|
||||
|
|
|
@ -557,7 +557,11 @@ public class ModDependencyResolver {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return sets.stream().map(Objects::toString).collect(Collectors.joining(" || "));
|
||||
return toString(" || ");
|
||||
}
|
||||
|
||||
public String toString(String delimeter) {
|
||||
return sets.stream().map(Objects::toString).collect(Collectors.joining(delimeter));
|
||||
}
|
||||
|
||||
public Optional<SemVer> maxCompatible() {
|
||||
|
|
|
@ -83,9 +83,9 @@ public class Minecraft implements FrogPlugin {
|
|||
try {
|
||||
modProperties.retainAll(new ModDependencyResolver(modProperties).solve());
|
||||
} catch (ModDependencyResolver.BreakingModException e) {
|
||||
LoaderGui.builder().setContent(LoaderGui.ContentType.INFO_BREAKING_DEP, e).addReport(CrashReportGenerator.writeReport(e, modProperties)).build().show();
|
||||
LoaderGui.execBreakingDep(CrashReportGenerator.writeReport(e, modProperties), e, false);
|
||||
} catch (ModDependencyResolver.UnfulfilledDependencyException e) {
|
||||
LoaderGui.builder().setContent(LoaderGui.ContentType.INFO_UNFULFILLED_DEP, e).addReport(CrashReportGenerator.writeReport(e, modProperties)).build().show();
|
||||
LoaderGui.execUnfulfilledDep(CrashReportGenerator.writeReport(e, modProperties), e, false);
|
||||
}
|
||||
|
||||
mods.stream().filter(p -> modProperties.contains(modPaths.get(p))).map(Path::toUri).map(uri -> {
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
package dev.frogmc.frogloader.impl.util;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PlatformUtil {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PlatformUtil.class);
|
||||
|
||||
public static void open(URI uri) {
|
||||
LOGGER.info("Opening: {}", uri);
|
||||
|
||||
try {
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
|
||||
Desktop.getDesktop().browse(uri);
|
||||
} else {
|
||||
ProcessBuilder builder = new ProcessBuilder("xdg-open", uri.toString());
|
||||
builder.start();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to open url: ", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void copyStringContent(Path path) {
|
||||
try {
|
||||
if (System.getenv()
|
||||
.getOrDefault("DESKTOP_SESSION", "")
|
||||
.toLowerCase(Locale.ROOT)
|
||||
.contains("wayland")) {
|
||||
ProcessBuilder builder = new ProcessBuilder("bash", "-c", "wl-copy < " + path);
|
||||
builder.start();
|
||||
} else {
|
||||
String data = Files.readString(path, StandardCharsets.UTF_8);
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(data), null);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to copy contents of {}:", path, e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package dev.frogmc.frogloader.impl.util;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class URLUtil {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(URLUtil.class);
|
||||
|
||||
public static void open(URI url) {
|
||||
|
||||
LOGGER.info("Opening: {}", url);
|
||||
|
||||
try {
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
|
||||
Desktop.getDesktop().browse(url);
|
||||
} else {
|
||||
ProcessBuilder builder = new ProcessBuilder("xdg-open", url.toString());
|
||||
builder.start();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to open url: ", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void copyStringContent(URI url) {
|
||||
if (System.getenv().getOrDefault("DESKTOP_SESSION", "").toLowerCase(Locale.ROOT)
|
||||
.contains("wayland")) {
|
||||
try {
|
||||
ProcessBuilder builder = new ProcessBuilder("bash", "-c", "wl-copy < " + url);
|
||||
builder.start();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to copy contents of {}:", url, e);
|
||||
}
|
||||
} else {
|
||||
try (InputStream in = url.toURL().openStream();
|
||||
InputStreamReader inReader = new InputStreamReader(in);
|
||||
BufferedReader reader = new BufferedReader(inReader)) {
|
||||
String data = reader.lines().collect(Collectors.joining("\n"));
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(data), null);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to copy contents of {}:", url, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue