Add support for snapshots with Modrinth Servers (#3570)

* Add support for snapshots with Modrinth Servers

* Fix snapshots without dots

* Fix loader version not resetting when no longer valid

* Fix collapsing margins on Report page
This commit is contained in:
Prospector
2025-04-28 18:14:04 -07:00
committed by GitHub
parent 6f485d62ad
commit ea64e08791
3 changed files with 63 additions and 55 deletions

View File

@@ -53,7 +53,7 @@
</div> </div>
<div class="flex w-full flex-col gap-2 rounded-2xl bg-table-alternateRow p-4"> <div class="flex w-full flex-col gap-2 rounded-2xl bg-table-alternateRow p-4">
<div class="text-sm font-bold text-contrast">Minecraft version</div> <div class="text-lg font-bold text-contrast">Minecraft version</div>
<UiServersTeleportDropdownMenu <UiServersTeleportDropdownMenu
v-model="selectedMCVersion" v-model="selectedMCVersion"
name="mcVersion" name="mcVersion"
@@ -61,6 +61,20 @@
class="w-full max-w-[100%]" class="w-full max-w-[100%]"
placeholder="Select Minecraft version..." placeholder="Select Minecraft version..."
/> />
<div class="mt-2 flex items-center justify-between gap-2">
<label for="toggle-snapshots" class="font-semibold"> Show snapshot versions </label>
<div
v-tooltip="
isSnapshotSelected ? 'A snapshot version is currently selected.' : undefined
"
>
<Toggle
id="toggle-snapshots"
v-model="showSnapshots"
:disabled="isSnapshotSelected"
/>
</div>
</div>
</div> </div>
<div <div
@@ -74,7 +88,7 @@
}" }"
> >
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="text-sm font-bold text-contrast">{{ selectedLoader }} version</div> <div class="text-lg font-bold text-contrast">{{ selectedLoader }} version</div>
<template v-if="!selectedMCVersion"> <template v-if="!selectedMCVersion">
<div <div
@@ -177,8 +191,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { BackupWarning, ButtonStyled, NewModal } from "@modrinth/ui"; import { BackupWarning, ButtonStyled, NewModal, Toggle } from "@modrinth/ui";
import { RightArrowIcon, XIcon, ServerIcon, DropdownIcon } from "@modrinth/assets"; import { DropdownIcon, RightArrowIcon, ServerIcon, XIcon } from "@modrinth/assets";
import { $fetch } from "ofetch";
import type { Server } from "~/composables/pyroServers"; import type { Server } from "~/composables/pyroServers";
import type { Loaders } from "~/types/servers"; import type { Loaders } from "~/types/servers";
import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue"; import type { BackupInProgressReason } from "~/pages/servers/manage/[id].vue";
@@ -214,6 +229,7 @@ const hardReset = ref(false);
const isLoading = ref(false); const isLoading = ref(false);
const loadingServerCheck = ref(false); const loadingServerCheck = ref(false);
const serverCheckError = ref(""); const serverCheckError = ref("");
const showSnapshots = ref(false);
const selectedLoader = ref<Loaders>("Vanilla"); const selectedLoader = ref<Loaders>("Vanilla");
const selectedMCVersion = ref(""); const selectedMCVersion = ref("");
@@ -226,6 +242,22 @@ const cachedVersions = ref<VersionCache>({});
const versionStrings = ["forge", "fabric", "quilt", "neo"] as const; const versionStrings = ["forge", "fabric", "quilt", "neo"] as const;
const isSnapshotSelected = computed(() => {
if (selectedMCVersion.value) {
const selected = tags.value.gameVersions.find((x) => x.version === selectedMCVersion.value);
if (selected?.version_type !== "release") {
return true;
}
}
return false;
});
const getLoaderVersions = async (loader: string) => {
return await $fetch(
`https://launcher-meta.modrinth.com/${loader?.toLowerCase()}/v0/manifest.json`,
);
};
const fetchLoaderVersions = async () => { const fetchLoaderVersions = async () => {
const versions = await Promise.all( const versions = await Promise.all(
versionStrings.map(async (loader) => { versionStrings.map(async (loader) => {
@@ -234,7 +266,7 @@ const fetchLoaderVersions = async () => {
throw new Error("Failed to fetch loader versions"); throw new Error("Failed to fetch loader versions");
} }
try { try {
const res = await $fetch(`/loader-versions?loader=${loader}`); const res = await getLoaderVersions(loader);
return { [loader]: (res as any).gameVersions }; return { [loader]: (res as any).gameVersions };
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_) { } catch (_) {
@@ -277,11 +309,11 @@ const fetchPurpurVersions = async (mcVersion: string) => {
} }
}; };
const selectedLoaderVersions = computed(() => { const selectedLoaderVersions = computed<string[]>(() => {
const loader = selectedLoader.value.toLowerCase(); const loader = selectedLoader.value.toLowerCase();
if (loader === "paper") { if (loader === "paper") {
return paperVersions.value[selectedMCVersion.value] || []; return paperVersions.value[selectedMCVersion.value].map((x) => `${x}`) || [];
} }
if (loader === "purpur") { if (loader === "purpur") {
@@ -325,13 +357,22 @@ watch(selectedLoader, async () => {
watch( watch(
selectedLoaderVersions, selectedLoaderVersions,
(newVersions) => { (newVersions) => {
if (newVersions.length > 0 && !selectedLoaderVersion.value) { if (
newVersions.length > 0 &&
(!selectedLoaderVersion.value || !newVersions.includes(selectedLoaderVersion.value))
) {
selectedLoaderVersion.value = String(newVersions[0]); selectedLoaderVersion.value = String(newVersions[0]);
} }
}, },
{ immediate: true }, { immediate: true },
); );
const getLoaderVersion = async (loader: string, version: string) => {
return await $fetch(
`https://launcher-meta.modrinth.com/${loader?.toLowerCase()}/v0/versions/${version}.json`,
);
};
const checkVersionAvailability = async (version: string) => { const checkVersionAvailability = async (version: string) => {
if (!version || version.trim().length < 3) return; if (!version || version.trim().length < 3) return;
@@ -339,9 +380,7 @@ const checkVersionAvailability = async (version: string) => {
loadingServerCheck.value = true; loadingServerCheck.value = true;
try { try {
const mcRes = const mcRes = cachedVersions.value[version] || (await getLoaderVersion("minecraft", version));
cachedVersions.value[version] ||
(await $fetch(`/loader-versions?loader=minecraft&version=${version}`));
cachedVersions.value[version] = mcRes; cachedVersions.value[version] = mcRes;
@@ -377,13 +416,15 @@ onMounted(() => {
}); });
const tags = useTags(); const tags = useTags();
const mcVersions = tags.value.gameVersions const mcVersions = computed(() =>
.filter((x) => x.version_type === "release") tags.value.gameVersions
.map((x) => x.version) .filter((x) =>
.filter((x) => { showSnapshots.value
const segment = parseInt(x.split(".")[1], 10); ? x.version_type === "snapshot" || x.version_type === "release"
return !isNaN(segment) && segment > 2; : x.version_type === "release",
}); )
.map((x) => x.version),
);
const isDangerous = computed(() => hardReset.value); const isDangerous = computed(() => hardReset.value);
const canInstall = computed(() => { const canInstall = computed(() => {
@@ -448,6 +489,9 @@ const handleReinstall = async () => {
const onShow = () => { const onShow = () => {
selectedMCVersion.value = props.server.general?.mc_version || ""; selectedMCVersion.value = props.server.general?.mc_version || "";
if (isSnapshotSelected.value) {
showSnapshots.value = true;
}
}; };
const onHide = () => { const onHide = () => {

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="page"> <div class="page">
<div class="experimental-styles-within flex flex-col gap-2"> <div class="experimental-styles-within flex flex-col gap-2">
<RadialHeader class="top-box mb-2 text-center" color="orange"> <RadialHeader class="top-box mb-2 flex flex-col items-center justify-center" color="orange">
<ScaleIcon class="h-12 w-12 text-brand-orange" /> <ScaleIcon class="h-12 w-12 text-brand-orange" />
<h1 class="m-3 gap-2 text-3xl font-extrabold"> <h1 class="m-3 gap-2 text-3xl font-extrabold">
{{ {{

View File

@@ -1,36 +0,0 @@
const getLoaderVersions = async (loader: string) => {
const loaderVersions = await fetch(
`https://launcher-meta.modrinth.com/${loader?.toLowerCase()}/v0/manifest.json`,
);
return loaderVersions.json();
};
const getLoaderVersion = async (loader: string, version: string) => {
const loaderVersion = await fetch(
`https://launcher-meta.modrinth.com/${loader?.toLowerCase()}/v0/versions/${version}.json`,
);
return loaderVersion.json();
};
export default defineEventHandler(async (e) => {
const params = new URLSearchParams(e._path?.split("?")[1] ?? "");
if (!params.has("loader"))
return new Response(
JSON.stringify({
error: "Missing loader",
}),
{ status: 400, headers: { "Content-Type": "application/json" } },
);
const loader = params.get("loader");
const version = params.get("version");
if (version) {
const loaderVersion = await getLoaderVersion(loader!, version);
return new Response(JSON.stringify(loaderVersion), {
headers: { "Content-Type": "application/json" },
});
}
const loaderVersions = await getLoaderVersions(loader!);
return new Response(JSON.stringify(loaderVersions), {
headers: { "Content-Type": "application/json" },
});
});