fix(pyroservers): synchronize server icon processing

Signed-off-by: Evan Song <theevansong@gmail.com>
This commit is contained in:
Evan Song
2025-02-15 19:34:52 -07:00
parent 179166a1bb
commit f4f4da8166
2 changed files with 149 additions and 145 deletions

View File

@@ -296,98 +296,86 @@ const processImage = async (iconUrl: string | undefined) => {
return sharedImage.value;
}
const image = ref<string | null>(null);
const auth = await PyroFetch<JWTAuth>(`servers/${internalServerRefrence.value.serverId}/fs`);
try {
const fileData = await PyroFetch(`/download?path=/server-icon-original.png`, {
override: auth,
retry: false,
});
if (fileData instanceof Blob) {
if (import.meta.client) {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.src = URL.createObjectURL(fileData);
await new Promise<void>((resolve) => {
img.onload = () => {
canvas.width = 512;
canvas.height = 512;
ctx?.drawImage(img, 0, 0, 512, 512);
const dataURL = canvas.toDataURL("image/png");
internalServerRefrence.value.general.image = dataURL;
image.value = dataURL;
sharedImage.value = dataURL;
resolve();
};
});
}
}
} catch (error) {
if (error instanceof PyroServersFetchError && error.statusCode === 404) {
sharedImage.value = undefined;
} else {
console.error(error);
}
}
if (image.value === null && iconUrl) {
console.log("iconUrl", iconUrl);
const auth = await PyroFetch<JWTAuth>(`servers/${internalServerRefrence.value.serverId}/fs`);
try {
const response = await fetch(iconUrl);
const file = await response.blob();
const originalfile = new File([file], "server-icon-original.png", {
type: "image/png",
const fileData = await PyroFetch(`/download?path=/server-icon-original.png`, {
override: auth,
retry: false,
});
if (import.meta.client) {
const scaledFile = await new Promise<File>((resolve, reject) => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.src = URL.createObjectURL(file);
img.onload = () => {
canvas.width = 64;
canvas.height = 64;
ctx?.drawImage(img, 0, 0, 64, 64);
canvas.toBlob((blob) => {
if (blob) {
const data = new File([blob], "server-icon.png", { type: "image/png" });
resolve(data);
} else {
reject(new Error("Canvas toBlob failed"));
}
}, "image/png");
};
img.onerror = reject;
});
if (scaledFile) {
await PyroFetch(`/create?path=/server-icon.png&type=file`, {
method: "POST",
contentType: "application/octet-stream",
body: scaledFile,
override: auth,
});
await PyroFetch(`/create?path=/server-icon-original.png&type=file`, {
method: "POST",
contentType: "application/octet-stream",
body: originalfile,
override: auth,
if (fileData instanceof Blob) {
if (import.meta.client) {
const dataURL = await new Promise<string>((resolve) => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.onload = () => {
canvas.width = 512;
canvas.height = 512;
ctx?.drawImage(img, 0, 0, 512, 512);
resolve(canvas.toDataURL("image/png"));
URL.revokeObjectURL(img.src);
};
img.src = URL.createObjectURL(fileData);
});
sharedImage.value = dataURL;
return dataURL;
}
}
} catch (error) {
if (error instanceof PyroServersFetchError && error.statusCode === 404) {
console.log("[PYROSERVERS] No server icon found");
} else {
console.error(error);
if (error instanceof PyroServersFetchError && error.statusCode === 404 && iconUrl) {
try {
const response = await fetch(iconUrl);
if (!response.ok) throw new Error("Failed to fetch icon");
const file = await response.blob();
const originalFile = new File([file], "server-icon-original.png", { type: "image/png" });
if (import.meta.client) {
const dataURL = await new Promise<string>((resolve) => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.onload = () => {
canvas.width = 64;
canvas.height = 64;
ctx?.drawImage(img, 0, 0, 64, 64);
canvas.toBlob(async (blob) => {
if (blob) {
const scaledFile = new File([blob], "server-icon.png", { type: "image/png" });
await PyroFetch(`/create?path=/server-icon.png&type=file`, {
method: "POST",
contentType: "application/octet-stream",
body: scaledFile,
override: auth,
});
await PyroFetch(`/create?path=/server-icon-original.png&type=file`, {
method: "POST",
contentType: "application/octet-stream",
body: originalFile,
override: auth,
});
}
}, "image/png");
const dataURL = canvas.toDataURL("image/png");
resolve(dataURL);
URL.revokeObjectURL(img.src);
};
img.src = URL.createObjectURL(file);
});
sharedImage.value = dataURL;
return dataURL;
}
} catch (error) {
console.error("Failed to process external icon:", error);
}
}
}
} catch (error) {
console.error("Failed to process server icon:", error);
}
return image.value;
return undefined;
};
// ------------------ GENERAL ------------------ //
@@ -974,35 +962,15 @@ const modules: any = {
get: async (serverId: string) => {
try {
const data = await PyroFetch<General>(`servers/${serverId}`);
// TODO: temp hack to fix hydration error
if (data.upstream?.project_id) {
const res = await $fetch(
`https://api.modrinth.com/v2/project/${data.upstream.project_id}`,
);
data.project = res as Project;
if (data.project?.icon_url) {
const sharedImage = useState<string | undefined>(
`server-icon-${serverId}`,
() => undefined,
);
if (!sharedImage.value) {
data.image = (await processImage(data.project.icon_url)) ?? undefined;
sharedImage.value = data.image;
} else {
data.image = sharedImage.value;
}
}
}
const sharedImage = useState<string | undefined>(
`server-icon-${serverId}`,
() => undefined,
);
if (import.meta.client) {
data.image =
sharedImage.value ?? (await processImage(data.project?.icon_url)) ?? undefined;
data.image = (await processImage(data.project?.icon_url)) ?? undefined;
}
const motd = await getMotd();

View File

@@ -5,7 +5,7 @@
<div class="card flex flex-col gap-4">
<label for="server-name-field" class="flex flex-col gap-2">
<span class="text-lg font-bold text-contrast">Server name</span>
<span> Change your server's name. This name is only visible on Modrinth.</span>
<span> This name is only visible on Modrinth.</span>
</label>
<div class="flex flex-col gap-2">
<input
@@ -64,10 +64,7 @@
<div class="card flex flex-col gap-4">
<label for="server-icon-field" class="flex flex-col gap-2">
<span class="text-lg font-bold text-contrast">Server icon</span>
<span>
Change your server's icon. Changes will be visible on the Minecraft server list and on
Modrinth.
</span>
<span> This icon will be visible on the Minecraft server list and on Modrinth. </span>
</label>
<div class="flex gap-4">
<div
@@ -234,67 +231,106 @@ const resetGeneral = () => {
const uploadFile = async (e: Event) => {
const file = (e.target as HTMLInputElement).files?.[0];
// down scale the image to 64x64
if (!file) {
addNotification({
group: "serverOptions",
type: "error",
title: "No file selected",
text: "Please select a file to upload.",
});
return;
}
const scaledFile = await new Promise<File>((resolve, reject) => {
if (!file) {
addNotification({
group: "serverOptions",
type: "error",
title: "No file selected",
text: "Please select a file to upload.",
});
reject(new Error("No file selected"));
return;
}
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
img.src = URL.createObjectURL(file);
img.onload = () => {
canvas.width = 64;
canvas.height = 64;
ctx?.drawImage(img, 0, 0, 64, 64);
// turn the downscaled image back to a png file
canvas.toBlob((blob) => {
if (blob) {
const data = new File([blob], "server-icon.png", { type: "image/png" });
resolve(data);
resolve(new File([blob], "server-icon.png", { type: "image/png" }));
} else {
reject(new Error("Canvas toBlob failed"));
}
}, "image/png");
URL.revokeObjectURL(img.src);
};
img.onerror = reject;
img.src = URL.createObjectURL(file);
});
if (!file) return;
if (data.value?.image) {
await props.server.fs?.deleteFileOrFolder("/server-icon.png", false);
await props.server.fs?.deleteFileOrFolder("/server-icon-original.png", false);
}
await props.server.fs?.uploadFile("/server-icon.png", scaledFile);
await props.server.fs?.uploadFile("/server-icon-original.png", file);
await props.server.refresh();
addNotification({
group: "serverOptions",
type: "success",
title: "Server icon updated",
text: "Your server icon was successfully changed.",
});
try {
if (data.value?.image) {
await props.server.fs?.deleteFileOrFolder("/server-icon.png", false);
await props.server.fs?.deleteFileOrFolder("/server-icon-original.png", false);
}
await props.server.fs?.uploadFile("/server-icon.png", scaledFile);
await props.server.fs?.uploadFile("/server-icon-original.png", file);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
await new Promise<void>((resolve) => {
img.onload = () => {
canvas.width = 512;
canvas.height = 512;
ctx?.drawImage(img, 0, 0, 512, 512);
const dataURL = canvas.toDataURL("image/png");
useState(`server-icon-${props.server.serverId}`).value = dataURL;
if (data.value) data.value.image = dataURL;
resolve();
URL.revokeObjectURL(img.src);
};
img.src = URL.createObjectURL(file);
});
addNotification({
group: "serverOptions",
type: "success",
title: "Server icon updated",
text: "Your server icon was successfully changed.",
});
} catch (error) {
console.error("Error uploading icon:", error);
addNotification({
group: "serverOptions",
type: "error",
title: "Upload failed",
text: "Failed to upload server icon.",
});
}
};
const resetIcon = async () => {
if (data.value?.image) {
await props.server.fs?.deleteFileOrFolder("/server-icon.png", false);
await props.server.fs?.deleteFileOrFolder("/server-icon-original.png", false);
await new Promise((resolve) => setTimeout(resolve, 2000));
await reloadNuxtApp();
addNotification({
group: "serverOptions",
type: "success",
title: "Server icon reset",
text: "Your server icon was successfully reset.",
});
try {
await props.server.fs?.deleteFileOrFolder("/server-icon.png", false);
await props.server.fs?.deleteFileOrFolder("/server-icon-original.png", false);
useState(`server-icon-${props.server.serverId}`).value = undefined;
if (data.value) data.value.image = undefined;
await props.server.refresh(["general"]);
addNotification({
group: "serverOptions",
type: "success",
title: "Server icon reset",
text: "Your server icon was successfully reset.",
});
} catch (error) {
console.error("Error resetting icon:", error);
addNotification({
group: "serverOptions",
type: "error",
title: "Reset failed",
text: "Failed to reset server icon.",
});
}
}
};