mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-03-28 19:23:15 +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);
|
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
|
// Parse and check all submission info
|
||||||
const parsed = submitSchema.safeParse({
|
const parsed = submitSchema.safeParse({
|
||||||
platform: formData.get("platform"),
|
platform: formData.get("platform"),
|
||||||
|
|
@ -100,7 +128,7 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
gender: formData.get("gender") ?? undefined, // ZOD MOMENT
|
gender: formData.get("gender") ?? undefined, // ZOD MOMENT
|
||||||
miiPortraitImage: formData.get("miiPortraitImage"),
|
miiPortraitImage: formData.get("miiPortraitImage"),
|
||||||
instructions: JSON.parse((formData.get("instructions") as string) ?? {}),
|
instructions: minifiedInstructions,
|
||||||
|
|
||||||
qrBytesRaw: rawQrBytesRaw,
|
qrBytesRaw: rawQrBytesRaw,
|
||||||
|
|
||||||
|
|
@ -156,35 +184,9 @@ export async function POST(request: NextRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Mii portrait image as well (Switch)
|
// Check Mii portrait image as well (Switch)
|
||||||
let minifiedInstructions: Partial<SwitchMiiInstructions>;
|
|
||||||
if (platform === "SWITCH") {
|
if (platform === "SWITCH") {
|
||||||
const imageValidation = await validateImage(miiPortraitImage);
|
const imageValidation = await validateImage(miiPortraitImage);
|
||||||
if (!imageValidation.valid) return rateLimit.sendResponse({ error: imageValidation.error }, imageValidation.status ?? 400);
|
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 ?? []);
|
const qrBytes = new Uint8Array(qrBytesRaw ?? []);
|
||||||
|
|
@ -220,7 +222,7 @@ export async function POST(request: NextRequest) {
|
||||||
allowedCopying: conversion.mii.allowCopying,
|
allowedCopying: conversion.mii.allowCopying,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
instructions,
|
instructions: minifiedInstructions,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ export default async function MiiPage({ params }: Props) {
|
||||||
alt="mii headshot"
|
alt="mii headshot"
|
||||||
width={250}
|
width={250}
|
||||||
height={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>
|
</div>
|
||||||
{/* QR Code */}
|
{/* QR Code */}
|
||||||
|
|
@ -303,6 +303,9 @@ export default async function MiiPage({ params }: Props) {
|
||||||
</Link>
|
</Link>
|
||||||
{mii.platform === "THREE_DS" ? <ThreeDsScanTutorialButton /> : <SwitchScanTutorialButton />}
|
{mii.platform === "THREE_DS" ? <ThreeDsScanTutorialButton /> : <SwitchScanTutorialButton />}
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,6 @@ export default function SubmitForm() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert QR code to JS (3DS)
|
// Convert QR code to JS (3DS)
|
||||||
if (platform === "THREE_DS") {
|
|
||||||
let conversion: { mii: Mii; tomodachiLifeMii: ThreeDsTomodachiLifeMii };
|
let conversion: { mii: Mii; tomodachiLifeMii: ThreeDsTomodachiLifeMii };
|
||||||
try {
|
try {
|
||||||
conversion = convertQrCode(qrBytes);
|
conversion = convertQrCode(qrBytes);
|
||||||
|
|
@ -182,7 +181,6 @@ export default function SubmitForm() {
|
||||||
setError(error instanceof Error ? error.message : String(error));
|
setError(error instanceof Error ? error.message : String(error));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a new QR code for aesthetic reasons
|
// Generate a new QR code for aesthetic reasons
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export default function GlassesTab({ instructions }: Props) {
|
||||||
|
|
||||||
<div className="flex justify-center h-74 mt-auto">
|
<div className="flex justify-center h-74 mt-auto">
|
||||||
<TypeSelector
|
<TypeSelector
|
||||||
|
hasNoneOption
|
||||||
length={50}
|
length={50}
|
||||||
type={type}
|
type={type}
|
||||||
setType={(i) => {
|
setType={(i) => {
|
||||||
|
|
|
||||||
|
|
@ -134,31 +134,38 @@ export async function generateMetadataImage(mii: Mii, author: string): Promise<{
|
||||||
fs.readFile(path.join(miiUploadsDirectory, "mii.webp")).then((buffer) =>
|
fs.readFile(path.join(miiUploadsDirectory, "mii.webp")).then((buffer) =>
|
||||||
sharp(buffer)
|
sharp(buffer)
|
||||||
.png()
|
.png()
|
||||||
|
// extend to fix shadow bug on landscape pictures
|
||||||
|
.extend({
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
||||||
|
})
|
||||||
.toBuffer()
|
.toBuffer()
|
||||||
.then((pngBuffer) => `data:image/png;base64,${pngBuffer.toString("base64")}`),
|
.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)
|
sharp(buffer)
|
||||||
.png()
|
.png()
|
||||||
.toBuffer()
|
.toBuffer()
|
||||||
.then((pngBuffer) => `data:image/png;base64,${pngBuffer.toString("base64")}`),
|
.then((pngBuffer) => `data:image/png;base64,${pngBuffer.toString("base64")}`),
|
||||||
),
|
)
|
||||||
|
: Promise.resolve(null),
|
||||||
loadFonts(),
|
loadFonts(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const jsx: ReactNode = (
|
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="w-full h-full bg-amber-50 border-2 border-amber-500 rounded-2xl p-4 flex flex-col">
|
||||||
<div tw="flex w-full">
|
<div tw="flex w-full">
|
||||||
{/* Mii image */}
|
{/* Mii portrait */}
|
||||||
<div
|
<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={{
|
style={{
|
||||||
backgroundImage: "linear-gradient(to bottom, #fef3c7, #fde68a);",
|
backgroundImage: "linear-gradient(to bottom, #fef3c7, #fde68a);",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={miiImage}
|
src={miiImage}
|
||||||
width={248}
|
|
||||||
height={248}
|
height={248}
|
||||||
tw="w-full h-full"
|
tw="w-full h-full"
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -169,9 +176,19 @@ export async function generateMetadataImage(mii: Mii, author: string): Promise<{
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* QR code */}
|
{/* QR code */}
|
||||||
|
{mii.platform === "THREE_DS" ? (
|
||||||
<div tw="w-60 bg-amber-200 rounded-xl flex justify-center items-center">
|
<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>
|
||||||
|
) : (
|
||||||
|
<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>
|
||||||
|
|
||||||
<div tw="flex flex-col w-full h-30 relative">
|
<div tw="flex flex-col w-full h-30 relative">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue