mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-03-28 11:13:16 +00:00
feat: metadata images for switch platform
also some other changes
This commit is contained in:
parent
e31141ea39
commit
5995afe3db
5 changed files with 71 additions and 50 deletions
|
|
@ -91,6 +91,34 @@ export async function POST(request: NextRequest) {
|
|||
return rateLimit.sendResponse({ error: "Invalid JSON in tags or QR code data" }, 400);
|
||||
}
|
||||
|
||||
// Minify instructions to save space and improve user experience
|
||||
let minifiedInstructions: Partial<SwitchMiiInstructions> | undefined;
|
||||
if (formData.get("platform") === "SWITCH") {
|
||||
function minify(object: Partial<SwitchMiiInstructions>): Partial<SwitchMiiInstructions> {
|
||||
for (const key in object) {
|
||||
const value = object[key as keyof SwitchMiiInstructions];
|
||||
|
||||
if (!value) {
|
||||
delete object[key as keyof SwitchMiiInstructions];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Recurse into nested objects
|
||||
if (typeof value === "object") {
|
||||
minify(value as Partial<SwitchMiiInstructions>);
|
||||
|
||||
if (Object.keys(value).length === 0) {
|
||||
delete object[key as keyof SwitchMiiInstructions];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
minifiedInstructions = minify(JSON.parse((formData.get("instructions") as string) ?? "{}") as SwitchMiiInstructions);
|
||||
}
|
||||
|
||||
// Parse and check all submission info
|
||||
const parsed = submitSchema.safeParse({
|
||||
platform: formData.get("platform"),
|
||||
|
|
@ -100,7 +128,7 @@ export async function POST(request: NextRequest) {
|
|||
|
||||
gender: formData.get("gender") ?? undefined, // ZOD MOMENT
|
||||
miiPortraitImage: formData.get("miiPortraitImage"),
|
||||
instructions: JSON.parse((formData.get("instructions") as string) ?? {}),
|
||||
instructions: minifiedInstructions,
|
||||
|
||||
qrBytesRaw: rawQrBytesRaw,
|
||||
|
||||
|
|
@ -156,35 +184,9 @@ export async function POST(request: NextRequest) {
|
|||
}
|
||||
|
||||
// Check Mii portrait image as well (Switch)
|
||||
let minifiedInstructions: Partial<SwitchMiiInstructions>;
|
||||
if (platform === "SWITCH") {
|
||||
const imageValidation = await validateImage(miiPortraitImage);
|
||||
if (!imageValidation.valid) return rateLimit.sendResponse({ error: imageValidation.error }, imageValidation.status ?? 400);
|
||||
|
||||
// Minimize instructions to save space and improve user experience
|
||||
function minimize(object: Partial<SwitchMiiInstructions>): Partial<SwitchMiiInstructions> {
|
||||
for (const key in object) {
|
||||
const value = object[key as keyof SwitchMiiInstructions];
|
||||
|
||||
if (!value) {
|
||||
delete object[key as keyof SwitchMiiInstructions];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Recurse into nested objects
|
||||
if (typeof value === "object") {
|
||||
minimize(value as Partial<SwitchMiiInstructions>);
|
||||
|
||||
if (Object.keys(value).length === 0) {
|
||||
delete object[key as keyof SwitchMiiInstructions];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
minifiedInstructions = minimize(instructions as SwitchMiiInstructions);
|
||||
}
|
||||
|
||||
const qrBytes = new Uint8Array(qrBytesRaw ?? []);
|
||||
|
|
@ -220,7 +222,7 @@ export async function POST(request: NextRequest) {
|
|||
allowedCopying: conversion.mii.allowCopying,
|
||||
}
|
||||
: {
|
||||
instructions,
|
||||
instructions: minifiedInstructions,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export default async function MiiPage({ params }: Props) {
|
|||
alt="mii headshot"
|
||||
width={250}
|
||||
height={250}
|
||||
className="drop-shadow-lg hover:scale-105 transition-transform"
|
||||
className="drop-shadow-lg hover:scale-105 transition-transform w-full max-h-96 object-contain"
|
||||
/>
|
||||
</div>
|
||||
{/* QR Code */}
|
||||
|
|
@ -303,6 +303,9 @@ export default async function MiiPage({ params }: Props) {
|
|||
</Link>
|
||||
{mii.platform === "THREE_DS" ? <ThreeDsScanTutorialButton /> : <SwitchScanTutorialButton />}
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<div className="bg-amber-50 border-2 border-amber-500 rounded-2xl shadow-lg p-4">{JSON.stringify(mii.instructions)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -173,7 +173,6 @@ export default function SubmitForm() {
|
|||
}
|
||||
|
||||
// Convert QR code to JS (3DS)
|
||||
if (platform === "THREE_DS") {
|
||||
let conversion: { mii: Mii; tomodachiLifeMii: ThreeDsTomodachiLifeMii };
|
||||
try {
|
||||
conversion = convertQrCode(qrBytes);
|
||||
|
|
@ -182,7 +181,6 @@ export default function SubmitForm() {
|
|||
setError(error instanceof Error ? error.message : String(error));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a new QR code for aesthetic reasons
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ export default function GlassesTab({ instructions }: Props) {
|
|||
|
||||
<div className="flex justify-center h-74 mt-auto">
|
||||
<TypeSelector
|
||||
hasNoneOption
|
||||
length={50}
|
||||
type={type}
|
||||
setType={(i) => {
|
||||
|
|
|
|||
|
|
@ -134,31 +134,38 @@ export async function generateMetadataImage(mii: Mii, author: string): Promise<{
|
|||
fs.readFile(path.join(miiUploadsDirectory, "mii.webp")).then((buffer) =>
|
||||
sharp(buffer)
|
||||
.png()
|
||||
// extend to fix shadow bug on landscape pictures
|
||||
.extend({
|
||||
left: 16,
|
||||
right: 16,
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
||||
})
|
||||
.toBuffer()
|
||||
.then((pngBuffer) => `data:image/png;base64,${pngBuffer.toString("base64")}`),
|
||||
),
|
||||
fs.readFile(path.join(miiUploadsDirectory, "qr-code.webp")).then((buffer) =>
|
||||
mii.platform === "THREE_DS"
|
||||
? fs.readFile(path.join(miiUploadsDirectory, "qr-code.webp")).then((buffer) =>
|
||||
sharp(buffer)
|
||||
.png()
|
||||
.toBuffer()
|
||||
.then((pngBuffer) => `data:image/png;base64,${pngBuffer.toString("base64")}`),
|
||||
),
|
||||
)
|
||||
: Promise.resolve(null),
|
||||
loadFonts(),
|
||||
]);
|
||||
|
||||
const jsx: ReactNode = (
|
||||
<div tw="w-full h-full bg-amber-50 border-2 border-amber-500 rounded-2xl p-4 flex flex-col">
|
||||
<div tw="flex w-full">
|
||||
{/* Mii image */}
|
||||
{/* Mii portrait */}
|
||||
<div
|
||||
tw="w-80 h-62 rounded-xl flex justify-center mr-2 px-2"
|
||||
tw={`h-62 rounded-xl flex justify-center items-center mr-2 ${mii.platform === "THREE_DS" ? "w-80" : "w-100"}`}
|
||||
style={{
|
||||
backgroundImage: "linear-gradient(to bottom, #fef3c7, #fde68a);",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={miiImage}
|
||||
width={248}
|
||||
height={248}
|
||||
tw="w-full h-full"
|
||||
style={{
|
||||
|
|
@ -169,9 +176,19 @@ export async function generateMetadataImage(mii: Mii, author: string): Promise<{
|
|||
</div>
|
||||
|
||||
{/* QR code */}
|
||||
{mii.platform === "THREE_DS" ? (
|
||||
<div tw="w-60 bg-amber-200 rounded-xl flex justify-center items-center">
|
||||
<img src={qrCodeImage} width={190} height={190} tw="border-2 border-amber-300 rounded-lg" />
|
||||
<img src={qrCodeImage!} width={190} height={190} tw="border-2 border-amber-300 rounded-lg" />
|
||||
</div>
|
||||
) : (
|
||||
<div tw="w-40 bg-amber-200 rounded-xl flex flex-col justify-center items-center p-6">
|
||||
<span tw="text-amber-900 font-extrabold text-xl text-center leading-tight">Switch Guide</span>
|
||||
<p tw="text-amber-800 text-sm text-center mt-1.5">You need to manually create the Mii, visit site for instructions.</p>
|
||||
<div tw="mt-auto bg-amber-600 rounded-lg w-full py-2 flex justify-center">
|
||||
<span tw="text-white font-semibold">View Steps</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div tw="flex flex-col w-full h-30 relative">
|
||||
|
|
|
|||
Loading…
Reference in a new issue