feat: nvm no automatic instructions

This commit is contained in:
trafficlunar 2026-04-15 21:01:35 +01:00
parent 43645eb782
commit c00eec3501
5 changed files with 39 additions and 71 deletions

View file

@ -49,7 +49,7 @@ const submitSchema = z.object({
// Save data way
miiDataFile: z
.instanceof(File)
.refine((blob) => blob.size < 1024 * 1024 * 1.5, "File too large") // TODO: actual size
.refine((blob) => blob.size < 1024 * 1024 * 1, "File too large") // TODO: actual size
.optional(),
// Manual way
@ -97,7 +97,7 @@ export async function POST(request: NextRequest) {
// Minify instructions to save space and improve user experience
let minifiedInstructions: Partial<SwitchMiiInstructions> | undefined;
if (formData.get("platform") === "SWITCH" && formData.get("way") === "manual")
if (formData.get("platform") === "SWITCH")
minifiedInstructions = minifyInstructions(JSON.parse((formData.get("instructions") as string) ?? "{}") as SwitchMiiInstructions);
// Parse and check all submission info
@ -211,8 +211,13 @@ export async function POST(request: NextRequest) {
if (way === "savedata") {
if (!miiData || !miiDataFileBuffer) return rateLimit.sendResponse({ error: "No valid Mii data provided" }, 400);
parsedSwitchMii = new SwitchTomodachiLifeMii(miiDataFileBuffer, miiData);
minifiedInstructions = parsedSwitchMii.toInstructions();
try {
parsedSwitchMii = new SwitchTomodachiLifeMii(miiDataFileBuffer, miiData);
} catch (error) {
console.warn("Failed to verify Switch Mii data", error);
return rateLimit.sendResponse({ error: "Failed to verify Mii data: is your ShareMii file up to date?" }, 400);
}
// minifiedInstructions = parsedSwitchMii.toInstructions();
}
// Create Mii in database
@ -285,7 +290,7 @@ export async function POST(request: NextRequest) {
if (parsedSwitchMii) {
const pngBuffer = await parsedSwitchMii.extractFacePaintImage();
if (pngBuffer) {
const fileLocation = path.join(miiUploadsDirectory, "features.png"); // Save as features because it isn't used
const fileLocation = path.join(miiUploadsDirectory, "facepaint.png");
await fs.writeFile(fileLocation, pngBuffer);
}
} else {

View file

@ -12,8 +12,8 @@ import { prisma } from "@/lib/prisma";
const searchParamsSchema = z.object({
type: z
.enum(["mii", "qr-code", "features", "image0", "image1", "image2", "metadata"], {
message: "Image type must be either 'mii', 'qr-code', 'features', 'image[number from 0 to 2]' or 'metadata'",
.enum(["mii", "qr-code", "features", "facepaint", "image0", "image1", "image2", "metadata"], {
message: "Image type must be either 'mii', 'qr-code', 'features', 'facepaint', 'image[number from 0 to 2]' or 'metadata'",
})
.default("mii"),
});

View file

@ -164,23 +164,13 @@ export default async function MiiPage({ params }: Props) {
/>
</div>
) : (
<>
{mii.isFromSaveFile && (
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium mb-4 w-full">
<hr className="grow border-zinc-300" />
<span>Face Paint Texture</span>
<hr className="grow border-zinc-300" />
</div>
)}
<ImageViewer
src={`/mii/${mii.id}/image?type=features`}
alt="mii features"
width={300}
height={300}
className="rounded-lg hover:brightness-90 mb-4 transition-all"
/>
</>
<ImageViewer
src={`/mii/${mii.id}/image?type=features`}
alt="mii features"
width={300}
height={300}
className="rounded-lg hover:brightness-90 mb-4 transition-all"
/>
)}
<hr className="w-full border-t-2 border-t-amber-400" />
@ -437,9 +427,9 @@ export default async function MiiPage({ params }: Props) {
Gallery
</h2>
{images.length > 0 ? (
{images.length > 0 || mii.isFromSaveFile ? (
<div className="grid grid-cols-3 gap-2 w-full max-md:grid-cols-2 max-[24rem]:grid-cols-1">
{images.map((src, index) => (
{[...(mii.isFromSaveFile ? [`/mii/${mii.id}/image?type=facepaint`] : []), ...images].map((src, index) => (
<div
key={index}
className="relative aspect-3/2 rounded-xl bg-black/65 border-2 border-amber-400 shadow-md overflow-hidden transition hover:shadow-lg shadow-black/30"

View file

@ -21,31 +21,13 @@ import MiiEditor from "./mii-editor";
import SwitchSubmitTutorialButton from "../tutorial/switch-submit";
import { Icon } from "@iconify/react";
import SwitchFileUpload from "./switch-file-upload";
import { deepMerge } from "@/lib/utils";
interface Props {
mii: Mii;
likes: number;
}
function deepMerge<T>(target: T, source: Partial<T>): T {
const output = structuredClone(target);
if (typeof source !== "object" || source === null) return output;
for (const key in source) {
const sourceValue = source[key];
const targetValue = (output as any)[key];
if (typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue)) {
(output as any)[key] = deepMerge(targetValue, sourceValue);
} else {
(output as any)[key] = sourceValue;
}
}
return output;
}
export default function EditForm({ mii, likes }: Props) {
const session = useSession();
const [files, setFiles] = useState<FileWithPath[]>([]);
@ -387,9 +369,11 @@ export default function EditForm({ mii, likes }: Props) {
</div>
<div className="flex flex-col items-center gap-2">
<SwitchFileUpload text="a screenshot of your Mii here" image={miiPortraitUri} setImage={handleMiiPortraitChange} forceCrop />
<SwitchFileUpload text="a screenshot of your Mii's features here" image={miiFeaturesUri} setImage={handleMiiFeaturesChange} />
<SwitchSubmitTutorialButton />
<SwitchFileUpload text="a screenshot of your Mii here" file={miiPortraitUri} setImage={handleMiiPortraitChange} forceCrop />
{!mii.isFromSaveFile && (
<SwitchFileUpload text="a screenshot of your Mii's features here" file={miiFeaturesUri} setImage={handleMiiFeaturesChange} />
)}
<SwitchSubmitTutorialButton type="manual" />
</div>
<p className="text-xs text-zinc-400 text-center mt-2">You must upload a screenshot of the features, check tutorial on how.</p>
@ -423,7 +407,7 @@ export default function EditForm({ mii, likes }: Props) {
</div>
<MiiEditor instructions={instructions} />
<SwitchSubmitTutorialButton />
<SwitchSubmitTutorialButton type="manual" />
</>
)}

View file

@ -477,8 +477,14 @@ export default function SubmitForm({ inQueueMiisCount }: Props) {
</div>
</div>
{way === "savedata" && (
<p className="text-xs text-zinc-400 text-center mb-2">
Unfortunately, at this time we can't automatically generate instructions from a .ltd file.
</p>
)}
{/* Step 2 - Features */}
<div className={`flex flex-col items-center gap-2 w-full ${way === "manual" ? "" : "hidden"}`}>
<div className="flex flex-col items-center gap-2 w-full">
<div className="flex items-center gap-2 self-start">
<span className="bg-orange-400 text-white text-xs font-bold rounded-full size-5 flex items-center justify-center shrink-0">2</span>
<span className="text-sm font-semibold text-zinc-600">
@ -544,31 +550,14 @@ export default function SubmitForm({ inQueueMiisCount }: Props) {
<SwitchFileUpload type="file" text="your Mii's .ltd file" file={miiDataFile} setFile={setMiiDataFile} />
<SwitchSubmitTutorialButton type="savedata" />
{/* YouTube */}
<div className="w-full grid grid-cols-3 items-center">
<label htmlFor="youtube" className="font-semibold">
YouTube Video (for makeup)
</label>
<input
id="youtube"
type="text"
className="pill input w-full col-span-2"
minLength={2}
maxLength={64}
placeholder="Paste a URL or video ID..."
value={youtubeId}
onChange={(e) => {
const val = e.target.value;
const match = val.match(/(?:youtube\.com\/(?:watch\?v=|shorts\/|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/);
setYouTubeId(match ? match[1] : val);
}}
/>
</div>
{way === "savedata" && (
<p className="text-xs text-zinc-400 text-center mb-2">Only the v3 format is supported, please make sure ShareMii is up to date.</p>
)}
</div>
</div>
{/* (Switch only) Mii instructions */}
<div className={`${platform === "SWITCH" && way === "manual" ? "" : "hidden"}`}>
<div className={`${platform === "SWITCH" && way ? "" : "hidden"}`}>
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium mt-8 mb-2">
<hr className="grow border-zinc-300" />
<span>Mii Instructions</span>