comparingLong(path ->
+ SafeFiles.getLastModifiedTime(path)
+ .map(FileTime::toMillis)
+ .orElse(0L))
+ .reversed())
+ .toList();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/src/client/java/dev/spiritstudios/snapper/util/SnapperUtil.java b/src/client/java/dev/spiritstudios/snapper/util/SnapperUtil.java
index 5f01f4c..54e18d9 100644
--- a/src/client/java/dev/spiritstudios/snapper/util/SnapperUtil.java
+++ b/src/client/java/dev/spiritstudios/snapper/util/SnapperUtil.java
@@ -6,49 +6,46 @@
import net.minecraft.util.Util;
import org.apache.commons.lang3.SystemProperties;
-import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-public class SnapperUtil {
- public static boolean inBoundingBox(int x, int y, int w, int h, double mouseX, double mouseY) {
- return mouseX > x && mouseX < x + w &&
- mouseY > y && mouseY < y + h;
- }
-
- public static Path getOSUnifiedFolder() {
- return switch (Util.getOperatingSystem()) {
- case WINDOWS -> Path.of(System.getenv("APPDATA"), ".snapper");
- case OSX -> Path.of(SystemProperties.getUserHome() + "/Library/Application Support", "snapper");
- default -> Path.of(SystemProperties.getUserHome(), ".snapper");
- };
- }
-
- public static Path getConfiguredScreenshotDirectory() {
- if (SnapperConfig.INSTANCE.useCustomScreenshotFolder.get()) {
- Path customPath = SnapperConfig.INSTANCE.customScreenshotFolder.get().resolve("screenshots");
- try {
- Files.createDirectories(customPath);
- } catch (IOException e) {
- Snapper.LOGGER.error("Failed to create directories of configured custom screenshot folder");
- }
- return customPath;
- }
- return MinecraftClient.getInstance().runDirectory.toPath().resolve("screenshots");
- }
-
- public static boolean isOfflineAccount() {
- return MinecraftClient.getInstance().getSession().getAccessToken().length() < 400;
- }
-
- public static boolean panoramaPresent(Path path) {
- if (!Files.exists(path)) return false;
- int partsPresent = 0;
-
- for (int i = 0; i < 6; i++) {
- if (Files.exists(path.resolve("panorama_%s.png".formatted(i)))) partsPresent++;
- }
-
- return partsPresent == 6;
- }
+public final class SnapperUtil {
+ public static final Path UNIFIED_FOLDER = switch (Util.getOperatingSystem()) {
+ case WINDOWS -> Path.of(System.getenv("APPDATA"), ".snapper");
+ case OSX -> Path.of(SystemProperties.getUserHome(), "Library", "Application Support", "snapper");
+ default -> Path.of(SystemProperties.getUserHome(), ".snapper");
+ };
+
+ public static boolean inBoundingBox(int x, int y, int w, int h, double mouseX, double mouseY) {
+ return mouseX > x && mouseX < x + w &&
+ mouseY > y && mouseY < y + h;
+ }
+
+ public static Path getConfiguredScreenshotDirectory() {
+ if (SnapperConfig.useCustomScreenshotFolder) {
+ Path customPath = SnapperConfig.customScreenshotFolder.resolve("screenshots");
+
+ if (!SafeFiles.createDirectories(customPath)) {
+ Snapper.LOGGER.error("Failed to create directories of configured custom screenshot folder");
+ }
+
+ return customPath;
+ }
+
+ return MinecraftClient.getInstance().runDirectory.toPath().resolve("screenshots");
+ }
+
+ public static boolean isOfflineAccount() {
+ return MinecraftClient.getInstance().getSession().getAccessToken().length() < 400;
+ }
+
+ public static boolean panoramaPresent(Path path) {
+ if (!Files.exists(path) || !Files.isDirectory(path)) return false;
+
+ for (int i = 0; i < 6; i++) {
+ if (!Files.exists(path.resolve("panorama_%s.png".formatted(i)))) return false;
+ }
+
+ return true;
+ }
}
diff --git a/src/client/java/dev/spiritstudios/snapper/util/UnreachableException.java b/src/client/java/dev/spiritstudios/snapper/util/UnreachableException.java
new file mode 100644
index 0000000..2766376
--- /dev/null
+++ b/src/client/java/dev/spiritstudios/snapper/util/UnreachableException.java
@@ -0,0 +1,17 @@
+package dev.spiritstudios.snapper.util;
+
+/**
+ * An exception indicating that a piece of code should be unreachable.
+ * Commonly used in switch statements to indicate that all possible cases have been handled.
+ *
+ * If this exception is thrown, it should be considered a bug in the code.
+ */
+public class UnreachableException extends RuntimeException {
+ public UnreachableException() {
+ super("This error should be impossible. If you see this, please report it!");
+ }
+
+ public UnreachableException(String message) {
+ super(message);
+ }
+}
\ No newline at end of file
diff --git a/src/client/java/dev/spiritstudios/snapper/util/WindowsActions.java b/src/client/java/dev/spiritstudios/snapper/util/WindowsActions.java
index b804f83..97a5cba 100644
--- a/src/client/java/dev/spiritstudios/snapper/util/WindowsActions.java
+++ b/src/client/java/dev/spiritstudios/snapper/util/WindowsActions.java
@@ -1,10 +1,14 @@
package dev.spiritstudios.snapper.util;
import dev.spiritstudios.snapper.Snapper;
+import org.jetbrains.annotations.NotNull;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
@@ -15,13 +19,16 @@
public class WindowsActions implements PlatformHelper {
@Override
public void copyScreenshot(Path path) {
- if (!Files.exists(path)) return;
+ if (!Files.exists(path)) {
+ Snapper.LOGGER.warn("Attempted to copy screenshot {} that does not exist", path);
+ return;
+ }
try (InputStream stream = Files.newInputStream(path)) {
BufferedImage imageBuffer = ImageIO.read(stream);
getClipboard().ifPresent(clipboard ->
- clipboard.setContents(new ScreenshotActions.TransferableImage(imageBuffer), null));
+ clipboard.setContents(new TransferableImage(imageBuffer), null));
} catch (IOException e) {
Snapper.LOGGER.error("Copying of image at {} failed", path);
}
@@ -32,8 +39,29 @@ private static Optional getClipboard() {
return Optional.of(Toolkit.getDefaultToolkit().getSystemClipboard());
} catch (HeadlessException e) {
Snapper.LOGGER.error("Failed to get clipboard", e);
- }
-
- return Optional.empty();
+ return Optional.empty();
+ }
}
+
+ record TransferableImage(Image image) implements Transferable {
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ return new DataFlavor[] {
+ DataFlavor.imageFlavor
+ };
+ }
+
+ @Override
+ public boolean isDataFlavorSupported(DataFlavor flavor) {
+ return DataFlavor.imageFlavor.equals(flavor);
+ }
+
+ @NotNull
+ @Override
+ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
+ if (!isDataFlavorSupported(flavor)) throw new UnsupportedFlavorException(flavor);
+
+ return image();
+ }
+ }
}
diff --git a/src/client/java/dev/spiritstudios/snapper/util/config/DirectoryConfigUtil.java b/src/client/java/dev/spiritstudios/snapper/util/config/DirectoryConfigUtil.java
index 4d89c25..45678aa 100644
--- a/src/client/java/dev/spiritstudios/snapper/util/config/DirectoryConfigUtil.java
+++ b/src/client/java/dev/spiritstudios/snapper/util/config/DirectoryConfigUtil.java
@@ -1,40 +1,14 @@
package dev.spiritstudios.snapper.util.config;
-import com.mojang.serialization.Codec;
-import com.mojang.serialization.DataResult;
-import dev.spiritstudios.snapper.gui.widget.FolderSelectWidget;
-import dev.spiritstudios.specter.api.config.Value;
import joptsimple.internal.Strings;
-import net.minecraft.client.gui.widget.ClickableWidget;
import org.apache.commons.lang3.SystemProperties;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
-import java.io.IOException;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
-import java.util.function.BiFunction;
public class DirectoryConfigUtil {
- public static final Codec PATH_CODEC = Codec.STRING.comapFlatMap(
- string -> {
- Path path = Path.of(string);
-
- try {
- Files.createDirectories(path);
- } catch (IOException e) {
- return DataResult.error(e::getMessage);
- }
-
- if (!Files.exists(path)) {
- return DataResult.error(() -> "Failed to get file from config string value. Does the directory exist?");
- }
-
- return DataResult.success(path);
- },
- path -> escapePath(path.toString())
- );
public static CompletableFuture> openFolderSelect(String title) {
return CompletableFuture.supplyAsync(() -> TinyFileDialogs.tinyfd_selectFolderDialog(title, SystemProperties.getUserHome()))
@@ -46,14 +20,4 @@ public static CompletableFuture> openFolderSelect(String title) {
return Optional.of(Path.of(selectedPath));
});
}
-
- public static final BiFunction, String, ? extends ClickableWidget> PATH_WIDGET_FACTORY = (configValue, id) -> {
- @SuppressWarnings("unchecked") Value value = (Value) configValue;
-
- return new FolderSelectWidget(0, 0, 10, 10, value, "%s.placeholder".formatted(configValue.translationKey(id)));
- };
-
- public static String escapePath(String path) {
- return path.replace("\\", "\\\\");
- }
}
\ No newline at end of file
diff --git a/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlClientApi.java b/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlClientApi.java
index 4dde727..63c09c5 100644
--- a/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlClientApi.java
+++ b/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlClientApi.java
@@ -11,6 +11,7 @@
import net.minecraft.util.Util;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
+import org.jetbrains.annotations.Nullable;
import java.io.Closeable;
import java.io.IOException;
@@ -42,7 +43,7 @@ public enum TermsAcceptance {
.build();
private Instant authTime = Instant.EPOCH;
- private AxolotlAuthentication auth;
+ private @Nullable AxolotlAuthentication auth;
public CompletableFuture uploadImage(Path image) {
byte[] bytes;
@@ -99,7 +100,7 @@ public CompletableFuture> post(String route, byte[] rawBody
}
private CompletableFuture> request(String route, Map query, byte[] rawBody, String method) {
- if (SnapperConfig.INSTANCE.termsAccepted.get() != TermsAcceptance.ACCEPTED)
+ if (SnapperConfig.termsAccepted != TermsAcceptance.ACCEPTED)
return CompletableFuture.failedFuture(new IllegalStateException("Terms not accepted"));
StringBuilder url = new StringBuilder(BASE_URL);
diff --git a/src/client/java/dev/spiritstudios/snapper/util/uploading/ScreenshotUploading.java b/src/client/java/dev/spiritstudios/snapper/util/uploading/ScreenshotUploading.java
index 73528be..e12be25 100644
--- a/src/client/java/dev/spiritstudios/snapper/util/uploading/ScreenshotUploading.java
+++ b/src/client/java/dev/spiritstudios/snapper/util/uploading/ScreenshotUploading.java
@@ -2,9 +2,9 @@
import dev.spiritstudios.snapper.Snapper;
import dev.spiritstudios.snapper.SnapperConfig;
+import dev.spiritstudios.snapper.SnapperConstants;
import dev.spiritstudios.snapper.gui.screen.PrivacyNoticeScreen;
import dev.spiritstudios.snapper.util.SnapperUtil;
-import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.toast.SystemToast;
import net.minecraft.text.Text;
@@ -14,16 +14,16 @@
public class ScreenshotUploading {
public static final String SNAPPER_WEB_URL = "https://snapper.spiritstudios.dev/img/%s";
- public static final String SNAPPER_VERSION = FabricLoader.getInstance().getModContainer("snapper")
- .orElseThrow()
- .getMetadata().getVersion().getFriendlyString();
+ public static final String SNAPPER_VERSION = SnapperConstants.VERSION;
private static final AxolotlClientApi API = new AxolotlClientApi();
+ public static final SystemToast.Type SCREENSHOT_UPLOAD_TOAST = new SystemToast.Type();
+
public static void toast(String title, String description, Object... args) {
MinecraftClient.getInstance().getToastManager().add(
SystemToast.create(MinecraftClient.getInstance(),
- SystemToast.Type.WORLD_BACKUP,
+ SCREENSHOT_UPLOAD_TOAST,
Text.translatable(title, args),
Text.translatable(description, args)));
}
@@ -34,7 +34,7 @@ public static CompletableFuture upload(Path image) {
return CompletableFuture.failedFuture(new IllegalStateException("Minecraft is currently running in offline mode."));
}
- if (SnapperConfig.INSTANCE.termsAccepted.get() == AxolotlClientApi.TermsAcceptance.UNSET) {
+ if (SnapperConfig.termsAccepted == AxolotlClientApi.TermsAcceptance.UNSET) {
MinecraftClient client = MinecraftClient.getInstance();
CompletableFuture success = new CompletableFuture<>();
@@ -45,7 +45,7 @@ public static CompletableFuture upload(Path image) {
return success;
}
- if (SnapperConfig.INSTANCE.termsAccepted.get() != AxolotlClientApi.TermsAcceptance.ACCEPTED) {
+ if (SnapperConfig.termsAccepted != AxolotlClientApi.TermsAcceptance.ACCEPTED) {
toast("toast.snapper.upload.failure", "toast.snapper.upload.axolotlclient.api_disabled");
return CompletableFuture.failedFuture(new IllegalStateException("AxolotlClient API is disabled."));
}
diff --git a/src/client/resources/snapper.mixins.json b/src/client/resources/mixins.snapper.json
similarity index 83%
rename from src/client/resources/snapper.mixins.json
rename to src/client/resources/mixins.snapper.json
index dc69796..21d58f6 100644
--- a/src/client/resources/snapper.mixins.json
+++ b/src/client/resources/mixins.snapper.json
@@ -1,9 +1,10 @@
{
"required": true,
"package": "dev.spiritstudios.snapper.mixin",
- "compatibilityLevel": "JAVA_21",
+ "compatibilityLevel": "${java_version}",
"client": [
"CameraMixin",
+ "EntryListWidgetMixin",
"GameMenuMixin",
"KeyboardMixin",
"MinecraftClientMixin",
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 37ca23b..359a21d 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -19,22 +19,17 @@
"environment": "client",
"entrypoints": {
"client": [
- "dev.spiritstudios.snapper.Snapper"
- ],
- "mixinsquared-adjuster": [
- "dev.spiritstudios.snapper.REMOVETHISAFTER1point21point5"
+ "dev.spiritstudios.snapper.SnapperEntrypoint"
]
},
- "accessWidener": "snapper.accesswidener",
"mixins": [
- "snapper.mixins.json"
+ "mixins.snapper.json"
],
"depends": {
- "fabricloader": ">=${fabric_loader_version}",
- "minecraft": "${minecraft_version}",
+ "fabricloader": ">=0.15.11",
+ "minecraft": "${mc_version}",
"fabric-api": "*",
- "java": ">=21",
- "specter-config": "*"
+ "java": ">=${java_version}"
},
"custom": {
"modmenu": {
diff --git a/src/main/resources/snapper.accesswidener b/src/main/resources/snapper.accesswidener
deleted file mode 100644
index 66736c7..0000000
--- a/src/main/resources/snapper.accesswidener
+++ /dev/null
@@ -1,2 +0,0 @@
-accessWidener v2 named
-extendable method net/minecraft/client/gui/widget/EntryListWidget getEntryAtPosition (DD)Lnet/minecraft/client/gui/widget/EntryListWidget$Entry;
\ No newline at end of file
diff --git a/versions/mainProject b/versions/mainProject
new file mode 100644
index 0000000..685ed77
--- /dev/null
+++ b/versions/mainProject
@@ -0,0 +1 @@
+1.21.5-fabric
\ No newline at end of file