mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-03-28 19:23:15 +00:00
feat: abandon webp
This commit is contained in:
parent
44be36b501
commit
22fb3a2e30
18 changed files with 34 additions and 39 deletions
|
|
@ -15,7 +15,7 @@ const nextConfig: NextConfig = {
|
|||
pathname: "/tutorial/**",
|
||||
},
|
||||
{
|
||||
pathname: "/guest.webp",
|
||||
pathname: "/guest.png",
|
||||
},
|
||||
],
|
||||
remotePatterns: [
|
||||
|
|
|
|||
BIN
public/guest.png
Normal file
BIN
public/guest.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
|
|
@ -49,7 +49,7 @@ export async function PATCH(request: NextRequest) {
|
|||
if (!image) {
|
||||
await prisma.user.update({
|
||||
where: { id: Number(session.user.id) },
|
||||
data: { image: `/guest.webp`, imageUpdatedAt: new Date() },
|
||||
data: { image: `/guest.png`, imageUpdatedAt: new Date() },
|
||||
});
|
||||
|
||||
return rateLimit.sendResponse({ success: true });
|
||||
|
|
@ -64,10 +64,10 @@ export async function PATCH(request: NextRequest) {
|
|||
|
||||
try {
|
||||
const buffer = Buffer.from(await image.arrayBuffer());
|
||||
const webpBuffer = await sharp(buffer, { animated: true }).resize({ width: 128, height: 128 }).webp({ quality: 85 }).toBuffer();
|
||||
const fileLocation = path.join(uploadsDirectory, `${session.user.id}.webp`);
|
||||
const pngBuffer = await sharp(buffer, { animated: true }).resize({ width: 128, height: 128 }).png({ quality: 85 }).toBuffer();
|
||||
const fileLocation = path.join(uploadsDirectory, `${session.user.id}.png`);
|
||||
|
||||
await fs.writeFile(fileLocation, webpBuffer);
|
||||
await fs.writeFile(fileLocation, pngBuffer);
|
||||
} catch (error) {
|
||||
console.error("Error uploading profile picture:", error);
|
||||
Sentry.captureException(error, { extra: { stage: "upload-profile-picture" } });
|
||||
|
|
|
|||
|
|
@ -126,10 +126,10 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
|
|||
await Promise.all(
|
||||
images.map(async (image, index) => {
|
||||
const buffer = Buffer.from(await image.arrayBuffer());
|
||||
const webpBuffer = await sharp(buffer).webp({ quality: 85 }).toBuffer();
|
||||
const fileLocation = path.join(miiUploadsDirectory, `image${index}.webp`);
|
||||
const pngBuffer = await sharp(buffer).png({ quality: 85 }).toBuffer();
|
||||
const fileLocation = path.join(miiUploadsDirectory, `image${index}.png`);
|
||||
|
||||
await fs.writeFile(fileLocation, webpBuffer);
|
||||
await fs.writeFile(fileLocation, pngBuffer);
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -148,10 +148,10 @@ export async function POST(request: NextRequest) {
|
|||
|
||||
try {
|
||||
// Compress and store
|
||||
const studioWebpBuffer = await sharp(studioBuffer).webp({ quality: 85 }).toBuffer();
|
||||
const studioFileLocation = path.join(miiUploadsDirectory, "mii.webp");
|
||||
const studioPngBuffer = await sharp(studioBuffer).png({ quality: 85 }).toBuffer();
|
||||
const studioFileLocation = path.join(miiUploadsDirectory, "mii.png");
|
||||
|
||||
await fs.writeFile(studioFileLocation, studioWebpBuffer);
|
||||
await fs.writeFile(studioFileLocation, studioPngBuffer);
|
||||
|
||||
// Generate a new QR code for aesthetic reasons
|
||||
const byteString = String.fromCharCode(...qrBytes);
|
||||
|
|
@ -165,10 +165,10 @@ export async function POST(request: NextRequest) {
|
|||
const codeBuffer = Buffer.from(codeBase64, "base64");
|
||||
|
||||
// Compress and store
|
||||
const codeWebpBuffer = await sharp(codeBuffer).webp({ quality: 85 }).toBuffer();
|
||||
const codeFileLocation = path.join(miiUploadsDirectory, "qr-code.webp");
|
||||
const codePngBuffer = await sharp(codeBuffer).png({ quality: 85 }).toBuffer();
|
||||
const codeFileLocation = path.join(miiUploadsDirectory, "qr-code.png");
|
||||
|
||||
await fs.writeFile(codeFileLocation, codeWebpBuffer);
|
||||
await fs.writeFile(codeFileLocation, codePngBuffer);
|
||||
await generateMetadataImage(miiRecord, session.user.name!);
|
||||
} catch (error) {
|
||||
// Clean up if something went wrong
|
||||
|
|
@ -184,10 +184,10 @@ export async function POST(request: NextRequest) {
|
|||
await Promise.all(
|
||||
images.map(async (image, index) => {
|
||||
const buffer = Buffer.from(await image.arrayBuffer());
|
||||
const webpBuffer = await sharp(buffer).webp({ quality: 85 }).toBuffer();
|
||||
const fileLocation = path.join(miiUploadsDirectory, `image${index}.webp`);
|
||||
const pngBuffer = await sharp(buffer).png({ quality: 85 }).toBuffer();
|
||||
const fileLocation = path.join(miiUploadsDirectory, `image${index}.png`);
|
||||
|
||||
await fs.writeFile(fileLocation, webpBuffer);
|
||||
await fs.writeFile(fileLocation, pngBuffer);
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|||
if (!searchParamsParsed.success) return rateLimit.sendResponse({ error: searchParamsParsed.error.issues[0].message }, 400);
|
||||
const { type: imageType } = searchParamsParsed.data;
|
||||
|
||||
const fileExtension = imageType === "metadata" ? ".png" : ".webp";
|
||||
const filePath = path.join(process.cwd(), "uploads", "mii", miiId.toString(), `${imageType}${fileExtension}`);
|
||||
const filePath = path.join(process.cwd(), "uploads", "mii", miiId.toString(), `${imageType}.png`);
|
||||
|
||||
let buffer: Buffer | undefined;
|
||||
// Only find Mii if image type is 'metadata'
|
||||
|
|
@ -109,7 +108,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|||
}
|
||||
|
||||
return rateLimit.sendResponse(buffer, 200, {
|
||||
"Content-Type": "image/webp",
|
||||
"Content-Type": "image/png",
|
||||
"X-Robots-Tag": "noindex, noimageindex, nofollow",
|
||||
"Cache-Control": "no-store",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|||
type: "profile",
|
||||
title: `${user.name} (@${user.username}) - TomodachiShare`,
|
||||
description: `View ${user.name}'s profile on TomodachiShare. Creator of ${user._count.miis} Miis. Member since ${joinDate}.`,
|
||||
images: [user.image ?? "/guest.webp"],
|
||||
images: [user.image ?? "/guest.png"],
|
||||
username: user.username,
|
||||
firstName: user.name,
|
||||
},
|
||||
|
|
@ -55,7 +55,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|||
card: "summary",
|
||||
title: `${user.name} (@${user.username}) - TomodachiShare`,
|
||||
description: `View ${user.name}'s profile on TomodachiShare. Creator of ${user._count.miis} Miis. Member since ${joinDate}.`,
|
||||
images: [user.image ?? "/guest.webp"],
|
||||
images: [user.image ?? "/guest.png"],
|
||||
creator: user.username!,
|
||||
},
|
||||
alternates: {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
|||
if (!parsed.success) return rateLimit.sendResponse({ error: parsed.error.issues[0].message }, 400);
|
||||
const userId = parsed.data;
|
||||
|
||||
const filePath = path.join(process.cwd(), "uploads", "user", `${userId}.webp`);
|
||||
const filePath = path.join(process.cwd(), "uploads", "user", `${userId}.png`);
|
||||
|
||||
try {
|
||||
const buffer = await fs.readFile(filePath);
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export default function Description({ text, className }: Props) {
|
|||
href={`/profile/${id}`}
|
||||
className="inline-flex items-center align-bottom gap-1.5 pr-2 bg-orange-100 border border-orange-400 rounded-lg mx-1 text-orange-800 text-xs"
|
||||
>
|
||||
<ProfilePicture src={linkedProfile.image || "/guest.webp"} width={24} height={24} className="bg-white rounded-lg border-r border-orange-400" />
|
||||
<ProfilePicture src={linkedProfile.image || "/guest.png"} width={24} height={24} className="bg-white rounded-lg border-r border-orange-400" />
|
||||
{linkedProfile.name}
|
||||
</Link>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export default function Dropzone({ onDrop, options, children }: Props) {
|
|||
onDrop: handleDrop,
|
||||
maxFiles: 3,
|
||||
accept: {
|
||||
"image/*": [".png", ".jpg", ".jpeg", ".bmp", ".webp", ".heic"],
|
||||
"image/*": [".png", ".jpg", ".jpeg", ".bmp", ".png", ".heic"],
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export default async function ProfileInformation({ userId, page }: Props) {
|
|||
<div className="flex w-full gap-4 overflow-x-scroll">
|
||||
{/* Profile picture */}
|
||||
<Link href={`/profile/${user.id}`} className="size-28 aspect-square">
|
||||
<ProfilePicture src={user.image ?? "/guest.webp"} className="rounded-full bg-white border-2 border-orange-400 shadow max-md:self-center" />
|
||||
<ProfilePicture src={user.image ?? "/guest.png"} className="rounded-full bg-white border-2 border-orange-400 shadow max-md:self-center" />
|
||||
</Link>
|
||||
{/* User information */}
|
||||
<div className="flex flex-col w-full relative py-3">
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default async function ProfileOverview() {
|
|||
<li title="Your profile">
|
||||
<Link href={`/profile/${session?.user.id}`} aria-label="Go to profile" className="pill button gap-2! p-0! h-full max-w-64" data-tooltip="Your Profile">
|
||||
<Image
|
||||
src={session?.user?.image ?? "/guest.webp"}
|
||||
src={session?.user?.image ?? "/guest.png"}
|
||||
alt="profile picture"
|
||||
width={40}
|
||||
height={40}
|
||||
|
|
|
|||
|
|
@ -7,5 +7,5 @@ export default function ProfilePicture(props: Partial<ImageProps>) {
|
|||
const { src, ...rest } = props;
|
||||
const [imgSrc, setImgSrc] = useState(src);
|
||||
|
||||
return <Image width={128} height={128} {...rest} src={imgSrc || "/guest.webp"} alt={"profile picture"} onError={() => setImgSrc("/guest.webp")} />;
|
||||
return <Image width={128} height={128} {...rest} src={imgSrc || "/guest.png"} alt={"profile picture"} onError={() => setImgSrc("/guest.png")} />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export default function ProfilePictureSettings() {
|
|||
</p>
|
||||
|
||||
<Image
|
||||
src={newPicture ? URL.createObjectURL(newPicture) : "/guest.webp"}
|
||||
src={newPicture ? URL.createObjectURL(newPicture) : "/guest.png"}
|
||||
alt="new profile picture"
|
||||
width={96}
|
||||
height={96}
|
||||
|
|
@ -93,7 +93,7 @@ export default function ProfilePictureSettings() {
|
|||
<div className="bg-orange-100 rounded-xl border-2 border-amber-500 mt-4 px-2 py-1 flex items-center">
|
||||
<p className="font-semibold mb-2">New profile picture:</p>
|
||||
<Image
|
||||
src={newPicture ? URL.createObjectURL(newPicture) : "/guest.webp"}
|
||||
src={newPicture ? URL.createObjectURL(newPicture) : "/guest.png"}
|
||||
alt="new profile picture"
|
||||
width={128}
|
||||
height={128}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export default function ReportUserForm({ user }: Props) {
|
|||
<hr className="border-zinc-300" />
|
||||
|
||||
<div className="bg-orange-100 rounded-xl border-2 border-orange-400 flex p-4 gap-4">
|
||||
<ProfilePicture src={user.image ?? "/guest.webp"} width={96} height={96} className="aspect-square rounded-full border-2 border-orange-400" />
|
||||
<ProfilePicture src={user.image ?? "/guest.png"} width={96} height={96} className="aspect-square rounded-full border-2 border-orange-400" />
|
||||
<div className="flex flex-col justify-center">
|
||||
<p className="text-xl font-bold overflow-hidden text-ellipsis">{user.name}</p>
|
||||
<p className="text-sm font-bold overflow-hidden text-ellipsis">@{user.username}</p>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export default function EditForm({ mii, likes }: Props) {
|
|||
const response = await fetch(path);
|
||||
const blob = await response.blob();
|
||||
|
||||
return Object.assign(new File([blob], `image${index}.webp`, { type: "image/webp" }), { path });
|
||||
return Object.assign(new File([blob], `image${index}.png`, { type: "image/png" }), { path });
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -130,16 +130,14 @@ export async function generateMetadataImage(mii: Mii, author: string): Promise<{
|
|||
|
||||
// Load assets concurrently
|
||||
const [miiImage, qrCodeImage, fonts] = await Promise.all([
|
||||
// Read and convert the .webp images to .png (because satori doesn't support it)
|
||||
fs.readFile(path.join(miiUploadsDirectory, "mii.webp")).then((buffer) =>
|
||||
// Read and convert the images to data URI
|
||||
fs.readFile(path.join(miiUploadsDirectory, "mii.png")).then((buffer) =>
|
||||
sharp(buffer)
|
||||
.png()
|
||||
.toBuffer()
|
||||
.then((pngBuffer) => `data:image/png;base64,${pngBuffer.toString("base64")}`),
|
||||
),
|
||||
fs.readFile(path.join(miiUploadsDirectory, "qr-code.webp")).then((buffer) =>
|
||||
fs.readFile(path.join(miiUploadsDirectory, "qr-code.png")).then((buffer) =>
|
||||
sharp(buffer)
|
||||
.png()
|
||||
.toBuffer()
|
||||
.then((pngBuffer) => `data:image/png;base64,${pngBuffer.toString("base64")}`),
|
||||
),
|
||||
|
|
@ -209,8 +207,6 @@ export async function generateMetadataImage(mii: Mii, author: string): Promise<{
|
|||
|
||||
// Store the file
|
||||
try {
|
||||
// I tried using .webp here but the quality looked awful
|
||||
// but it actually might be well-liked due to the hatred of .webp
|
||||
const fileLocation = path.join(miiUploadsDirectory, "metadata.png");
|
||||
await fs.writeFile(fileLocation, buffer);
|
||||
} catch (error) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue