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
|
// Save data way
|
||||||
miiDataFile: z
|
miiDataFile: z
|
||||||
.instanceof(File)
|
.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(),
|
.optional(),
|
||||||
|
|
||||||
// Manual way
|
// Manual way
|
||||||
|
|
@ -97,7 +97,7 @@ export async function POST(request: NextRequest) {
|
||||||
|
|
||||||
// Minify instructions to save space and improve user experience
|
// Minify instructions to save space and improve user experience
|
||||||
let minifiedInstructions: Partial<SwitchMiiInstructions> | undefined;
|
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);
|
minifiedInstructions = minifyInstructions(JSON.parse((formData.get("instructions") as string) ?? "{}") as SwitchMiiInstructions);
|
||||||
|
|
||||||
// Parse and check all submission info
|
// Parse and check all submission info
|
||||||
|
|
@ -211,8 +211,13 @@ export async function POST(request: NextRequest) {
|
||||||
if (way === "savedata") {
|
if (way === "savedata") {
|
||||||
if (!miiData || !miiDataFileBuffer) return rateLimit.sendResponse({ error: "No valid Mii data provided" }, 400);
|
if (!miiData || !miiDataFileBuffer) return rateLimit.sendResponse({ error: "No valid Mii data provided" }, 400);
|
||||||
|
|
||||||
parsedSwitchMii = new SwitchTomodachiLifeMii(miiDataFileBuffer, miiData);
|
try {
|
||||||
minifiedInstructions = parsedSwitchMii.toInstructions();
|
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
|
// Create Mii in database
|
||||||
|
|
@ -285,7 +290,7 @@ export async function POST(request: NextRequest) {
|
||||||
if (parsedSwitchMii) {
|
if (parsedSwitchMii) {
|
||||||
const pngBuffer = await parsedSwitchMii.extractFacePaintImage();
|
const pngBuffer = await parsedSwitchMii.extractFacePaintImage();
|
||||||
if (pngBuffer) {
|
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);
|
await fs.writeFile(fileLocation, pngBuffer);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
const searchParamsSchema = z.object({
|
const searchParamsSchema = z.object({
|
||||||
type: z
|
type: z
|
||||||
.enum(["mii", "qr-code", "features", "image0", "image1", "image2", "metadata"], {
|
.enum(["mii", "qr-code", "features", "facepaint", "image0", "image1", "image2", "metadata"], {
|
||||||
message: "Image type must be either 'mii', 'qr-code', 'features', 'image[number from 0 to 2]' or 'metadata'",
|
message: "Image type must be either 'mii', 'qr-code', 'features', 'facepaint', 'image[number from 0 to 2]' or 'metadata'",
|
||||||
})
|
})
|
||||||
.default("mii"),
|
.default("mii"),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -164,23 +164,13 @@ export default async function MiiPage({ params }: Props) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<ImageViewer
|
||||||
{mii.isFromSaveFile && (
|
src={`/mii/${mii.id}/image?type=features`}
|
||||||
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium mb-4 w-full">
|
alt="mii features"
|
||||||
<hr className="grow border-zinc-300" />
|
width={300}
|
||||||
<span>Face Paint Texture</span>
|
height={300}
|
||||||
<hr className="grow border-zinc-300" />
|
className="rounded-lg hover:brightness-90 mb-4 transition-all"
|
||||||
</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"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
<hr className="w-full border-t-2 border-t-amber-400" />
|
<hr className="w-full border-t-2 border-t-amber-400" />
|
||||||
|
|
||||||
|
|
@ -437,9 +427,9 @@ export default async function MiiPage({ params }: Props) {
|
||||||
Gallery
|
Gallery
|
||||||
</h2>
|
</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">
|
<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
|
<div
|
||||||
key={index}
|
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"
|
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 SwitchSubmitTutorialButton from "../tutorial/switch-submit";
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
import SwitchFileUpload from "./switch-file-upload";
|
import SwitchFileUpload from "./switch-file-upload";
|
||||||
|
import { deepMerge } from "@/lib/utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mii: Mii;
|
mii: Mii;
|
||||||
likes: number;
|
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) {
|
export default function EditForm({ mii, likes }: Props) {
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const [files, setFiles] = useState<FileWithPath[]>([]);
|
const [files, setFiles] = useState<FileWithPath[]>([]);
|
||||||
|
|
@ -387,9 +369,11 @@ export default function EditForm({ mii, likes }: Props) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col items-center gap-2">
|
<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 here" file={miiPortraitUri} setImage={handleMiiPortraitChange} forceCrop />
|
||||||
<SwitchFileUpload text="a screenshot of your Mii's features here" image={miiFeaturesUri} setImage={handleMiiFeaturesChange} />
|
{!mii.isFromSaveFile && (
|
||||||
<SwitchSubmitTutorialButton />
|
<SwitchFileUpload text="a screenshot of your Mii's features here" file={miiFeaturesUri} setImage={handleMiiFeaturesChange} />
|
||||||
|
)}
|
||||||
|
<SwitchSubmitTutorialButton type="manual" />
|
||||||
</div>
|
</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>
|
<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>
|
</div>
|
||||||
|
|
||||||
<MiiEditor instructions={instructions} />
|
<MiiEditor instructions={instructions} />
|
||||||
<SwitchSubmitTutorialButton />
|
<SwitchSubmitTutorialButton type="manual" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -477,8 +477,14 @@ export default function SubmitForm({ inQueueMiisCount }: Props) {
|
||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* 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">
|
<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="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">
|
<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} />
|
<SwitchFileUpload type="file" text="your Mii's .ltd file" file={miiDataFile} setFile={setMiiDataFile} />
|
||||||
<SwitchSubmitTutorialButton type="savedata" />
|
<SwitchSubmitTutorialButton type="savedata" />
|
||||||
|
|
||||||
{/* YouTube */}
|
{way === "savedata" && (
|
||||||
<div className="w-full grid grid-cols-3 items-center">
|
<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>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* (Switch only) Mii instructions */}
|
{/* (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">
|
<div className="flex items-center gap-4 text-zinc-500 text-sm font-medium mt-8 mb-2">
|
||||||
<hr className="grow border-zinc-300" />
|
<hr className="grow border-zinc-300" />
|
||||||
<span>Mii Instructions</span>
|
<span>Mii Instructions</span>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue