mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-05-13 13:17:45 +00:00
feat: nvm no automatic instructions
This commit is contained in:
parent
43645eb782
commit
c00eec3501
5 changed files with 39 additions and 71 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue