From 13941e849ce57b19735b997f9bb400dae6f1c374 Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Sat, 28 Feb 2026 16:57:43 +0000 Subject: [PATCH] feat: view instructions on mii page --- src/app/globals.css | 9 +- src/app/mii/[id]/page.tsx | 31 +- src/app/page.tsx | 4 +- src/app/profile/[id]/page.tsx | 4 +- src/app/profile/likes/page.tsx | 4 +- src/components/mii/dating-preferences.tsx | 38 +++ .../delete-mii-button.tsx} | 4 +- src/components/mii/instructions.tsx | 272 ++++++++++++++++++ .../{mii-list => mii/list}/filter-menu.tsx | 0 .../{mii-list => mii/list}/gender-select.tsx | 0 .../{mii-list => mii/list}/index.tsx | 6 +- .../{mii-list => mii/list}/other-filters.tsx | 0 .../{mii-list => mii/list}/pagination.tsx | 0 .../list}/platform-select.tsx | 0 .../{mii-list => mii/list}/skeleton.tsx | 0 .../{mii-list => mii/list}/sort-select.tsx | 0 .../{mii-list => mii/list}/tag-filter.tsx | 2 +- src/components/mii/personality-viewer.tsx | 49 ++++ src/components/{ => mii}/share-mii-button.tsx | 0 src/components/mii/voice-viewer.tsx | 59 ++++ .../submit-form/mii-editor/color-picker.tsx | 150 +--------- .../submit-form/mii-editor/tabs/misc.tsx | 168 +++-------- src/lib/switch.ts | 136 +++++++++ 23 files changed, 625 insertions(+), 311 deletions(-) create mode 100644 src/components/mii/dating-preferences.tsx rename src/components/{delete-mii.tsx => mii/delete-mii-button.tsx} (97%) create mode 100644 src/components/mii/instructions.tsx rename src/components/{mii-list => mii/list}/filter-menu.tsx (100%) rename src/components/{mii-list => mii/list}/gender-select.tsx (100%) rename src/components/{mii-list => mii/list}/index.tsx (98%) rename src/components/{mii-list => mii/list}/other-filters.tsx (100%) rename src/components/{mii-list => mii/list}/pagination.tsx (100%) rename src/components/{mii-list => mii/list}/platform-select.tsx (100%) rename src/components/{mii-list => mii/list}/skeleton.tsx (100%) rename src/components/{mii-list => mii/list}/sort-select.tsx (100%) rename src/components/{mii-list => mii/list}/tag-filter.tsx (97%) create mode 100644 src/components/mii/personality-viewer.tsx rename src/components/{ => mii}/share-mii-button.tsx (100%) create mode 100644 src/components/mii/voice-viewer.tsx create mode 100644 src/lib/switch.ts diff --git a/src/app/globals.css b/src/app/globals.css index e551000..add78f3 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -121,7 +121,7 @@ body { /* Range input */ input[type="range"] { - @apply appearance-none bg-transparent cursor-pointer; + @apply appearance-none bg-transparent not-disabled:cursor-pointer; } /* Track */ @@ -135,8 +135,7 @@ input[type="range"]::-moz-range-track { /* Thumb */ input[type="range"]::-webkit-slider-thumb { - @apply appearance-none size-4 bg-orange-400 border-2 border-orange-500 rounded-full shadow-md transition; - margin-top: -6px; /* center thumb vertically */ + @apply appearance-none size-4 bg-orange-400 border-2 border-orange-500 rounded-full shadow-md transition -mt-1.5; } input[type="range"]::-moz-range-thumb { @@ -145,9 +144,9 @@ input[type="range"]::-moz-range-thumb { /* Hover */ input[type="range"]:hover::-webkit-slider-thumb { - @apply bg-orange-500; + @apply not-disabled:bg-orange-500; } input[type="range"]:hover::-moz-range-thumb { - @apply bg-orange-500; + @apply not-disabled:bg-orange-500; } diff --git a/src/app/mii/[id]/page.tsx b/src/app/mii/[id]/page.tsx index 59d0011..ea5be47 100644 --- a/src/app/mii/[id]/page.tsx +++ b/src/app/mii/[id]/page.tsx @@ -7,15 +7,18 @@ import { Icon } from "@iconify/react"; import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; +import { MiiPlatform } from "@prisma/client"; import LikeButton from "@/components/like-button"; import ImageViewer from "@/components/image-viewer"; -import DeleteMiiButton from "@/components/delete-mii"; -import ShareMiiButton from "@/components/share-mii-button"; +import DeleteMiiButton from "@/components/mii/delete-mii-button"; +import ShareMiiButton from "@/components/mii/share-mii-button"; import ThreeDsScanTutorialButton from "@/components/tutorial/3ds-scan"; import SwitchScanTutorialButton from "@/components/tutorial/switch-scan"; import Description from "@/components/description"; -import { MiiPlatform } from "@prisma/client"; +import MiiInstructions from "@/components/mii/instructions"; + +import { SwitchMiiInstructions } from "@/types"; interface Props { params: Promise<{ id: string }>; @@ -120,7 +123,7 @@ export default async function MiiPage({ params }: Props) {
-
+
{/* Mii Image */}
-
- -
+ {mii.platform !== "THREE_DS" && ( +
+ +
+ )}
@@ -305,7 +310,7 @@ export default async function MiiPage({ params }: Props) {
{/* Instructions */} -
{JSON.stringify(mii.instructions)}
+ {mii.platform === "SWITCH" && } />}
@@ -343,7 +348,7 @@ export default async function MiiPage({ params }: Props) { ))} ) : ( -

There is nothing here...

+

There is nothing here...

)} diff --git a/src/app/page.tsx b/src/app/page.tsx index 801e5e7..40482a7 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -7,8 +7,8 @@ import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; import Countdown from "@/components/countdown"; -import MiiList from "@/components/mii-list"; -import Skeleton from "@/components/mii-list/skeleton"; +import MiiList from "@/components/mii/list"; +import Skeleton from "@/components/mii/list/skeleton"; interface Props { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; diff --git a/src/app/profile/[id]/page.tsx b/src/app/profile/[id]/page.tsx index 422d218..88dd476 100644 --- a/src/app/profile/[id]/page.tsx +++ b/src/app/profile/[id]/page.tsx @@ -5,8 +5,8 @@ import { Suspense } from "react"; import { prisma } from "@/lib/prisma"; import ProfileInformation from "@/components/profile-information"; -import MiiList from "@/components/mii-list"; -import Skeleton from "@/components/mii-list/skeleton"; +import MiiList from "@/components/mii/list"; +import Skeleton from "@/components/mii/list/skeleton"; interface Props { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; diff --git a/src/app/profile/likes/page.tsx b/src/app/profile/likes/page.tsx index abce75e..4d84cf6 100644 --- a/src/app/profile/likes/page.tsx +++ b/src/app/profile/likes/page.tsx @@ -5,8 +5,8 @@ import { Suspense } from "react"; import { auth } from "@/lib/auth"; import ProfileInformation from "@/components/profile-information"; -import Skeleton from "@/components/mii-list/skeleton"; -import MiiList from "@/components/mii-list"; +import Skeleton from "@/components/mii/list/skeleton"; +import MiiList from "@/components/mii/list"; interface Props { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; diff --git a/src/components/mii/dating-preferences.tsx b/src/components/mii/dating-preferences.tsx new file mode 100644 index 0000000..17844a9 --- /dev/null +++ b/src/components/mii/dating-preferences.tsx @@ -0,0 +1,38 @@ +import { ChangeEvent } from "react"; +import { MiiGender } from "@prisma/client"; +import { SwitchMiiInstructions } from "@/types"; + +interface Props { + data: SwitchMiiInstructions["datingPreferences"]; + onChecked?: (e: ChangeEvent, gender: MiiGender) => void; +} + +const DATING_PREFERENCES = ["Male", "Female", "Nonbinary"]; + +export default function DatingPreferencesViewer({ data, onChecked }: Props) { + return ( +
+ {DATING_PREFERENCES.map((gender) => { + const genderEnum = gender.toUpperCase() as MiiGender; + + return ( +
+ { + if (onChecked) onChecked(e, genderEnum); + }} + /> + +
+ ); + })} +
+ ); +} diff --git a/src/components/delete-mii.tsx b/src/components/mii/delete-mii-button.tsx similarity index 97% rename from src/components/delete-mii.tsx rename to src/components/mii/delete-mii-button.tsx index 8fe58e8..e5c4a0c 100644 --- a/src/components/delete-mii.tsx +++ b/src/components/mii/delete-mii-button.tsx @@ -6,8 +6,8 @@ import { useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { Icon } from "@iconify/react"; -import LikeButton from "./like-button"; -import SubmitButton from "./submit-button"; +import LikeButton from "../like-button"; +import SubmitButton from "../submit-button"; interface Props { miiId: number; diff --git a/src/components/mii/instructions.tsx b/src/components/mii/instructions.tsx new file mode 100644 index 0000000..716f9b9 --- /dev/null +++ b/src/components/mii/instructions.tsx @@ -0,0 +1,272 @@ +import React from "react"; + +import DatingPreferencesViewer from "./dating-preferences"; +import VoiceViewer from "./voice-viewer"; +import PersonalityViewer from "./personality-viewer"; + +import { SwitchMiiInstructions } from "@/types"; +import { Icon } from "@iconify/react"; +import { COLORS } from "@/lib/switch"; + +interface Props { + instructions: Partial; +} + +interface SectionProps { + name: string; + instructions: Partial; + children?: React.ReactNode; + isSubSection?: boolean; +} + +const ORDINAL_SUFFIXES: Record = { + one: "st", + two: "nd", + few: "rd", + other: "th", +}; +const ordinalRules = new Intl.PluralRules("en-US", { type: "ordinal" }); + +function GridPosition({ index, cols = 5 }: { index: number; cols?: number }) { + const row = Math.floor(index / cols) + 1; + const col = (index % cols) + 1; + const rowSuffix = ORDINAL_SUFFIXES[ordinalRules.select(row)]; + const colSuffix = ORDINAL_SUFFIXES[ordinalRules.select(col)]; + + return `${row}${rowSuffix} row, ${col}${colSuffix} column`; +} + +function ColorPosition({ color }: { color: number }) { + if (!color) return null; + if (color <= 7) { + return ( + <> + Color menu on left, + + ); + } + if (color >= 108) { + return ( + <> + Outside color menu, + + ); + } + + return ( + +
+ Color menu on right, +
+ ); +} + +interface TableCellProps { + label: string; + children: React.ReactNode; +} + +function TableCell({ label, children }: TableCellProps) { + return ( + + {label} + {children} + + ); +} + +function Section({ name, instructions, children, isSubSection }: SectionProps) { + if (typeof instructions !== "object") return null; + + const type = "type" in instructions ? instructions.type : undefined; + const color = "color" in instructions ? instructions.color : undefined; + const height = "height" in instructions ? instructions.height : undefined; + const distance = "distance" in instructions ? instructions.distance : undefined; + const rotation = "rotation" in instructions ? instructions.rotation : undefined; + const size = "size" in instructions ? instructions.size : undefined; + const stretch = "stretch" in instructions ? instructions.stretch : undefined; + + return ( +
+

{name}

+ + + + {type && ( + + + + )} + {color && ( + + + + )} + {height && {height}} + {distance && {distance}} + {rotation && {rotation}} + {size && {size}} + {stretch && {stretch}} + + {children} + +
+
+ ); +} + +export default function MiiInstructions({ instructions }: Props) { + if (Object.keys(instructions).length === 0) return null; + const { head, hair, eyebrows, eyes, nose, lips, ears, glasses, other, height, weight, datingPreferences, voice, personality } = instructions; + + return ( +
+

+ + Instructions +

+ + {head &&
} + {hair && ( +
+ {hair.setType && ( + + + + )} + {hair.bangsType && ( + + + + )} + {hair.backType && ( + + + + )} + {hair.subColor && ( + + + + )} +
+ )} + {eyebrows &&
} + {eyes && ( +
+ {eyes.eyesType && ( + + + + )} + {eyes.eyelashesTop && ( + + + + )} + {eyes.eyelashesBottom && ( + + + + )} + {eyes.eyelidTop && ( + + + + )} + {eyes.eyelidBottom && ( + + + + )} + {eyes.eyeliner && ( + + + + )} + {eyes.pupil && ( + + + + )} +
+ )} + {nose &&
} + {lips &&
} + {ears &&
} + {glasses && ( +
+ {glasses.ringColor && ( + + + + )} + {glasses.shadesColor && ( + + + + )} +
+ )} + {other && ( +
+
+
+
+
+
+
+
+
+
+ )} + + {(height || weight || datingPreferences || voice || personality) && ( +
+

Misc

+ + {height && ( +
+ + +
+ )} + {weight && ( +
+ + +
+ )} + {datingPreferences && ( +
+

Dating Preferences

+
+ +
+
+ )} + {voice && ( +
+

Voice

+
+ +
+
+ )} + {personality && ( +
+

Personality

+
+ +
+
+ )} +
+ )} +
+ ); +} diff --git a/src/components/mii-list/filter-menu.tsx b/src/components/mii/list/filter-menu.tsx similarity index 100% rename from src/components/mii-list/filter-menu.tsx rename to src/components/mii/list/filter-menu.tsx diff --git a/src/components/mii-list/gender-select.tsx b/src/components/mii/list/gender-select.tsx similarity index 100% rename from src/components/mii-list/gender-select.tsx rename to src/components/mii/list/gender-select.tsx diff --git a/src/components/mii-list/index.tsx b/src/components/mii/list/index.tsx similarity index 98% rename from src/components/mii-list/index.tsx rename to src/components/mii/list/index.tsx index c1dc2e6..1921b64 100644 --- a/src/components/mii-list/index.tsx +++ b/src/components/mii/list/index.tsx @@ -11,9 +11,9 @@ import { auth } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; import SortSelect from "./sort-select"; -import Carousel from "../carousel"; -import LikeButton from "../like-button"; -import DeleteMiiButton from "../delete-mii"; +import Carousel from "../../carousel"; +import LikeButton from "../../like-button"; +import DeleteMiiButton from "../delete-mii-button"; import Pagination from "./pagination"; import FilterMenu from "./filter-menu"; diff --git a/src/components/mii-list/other-filters.tsx b/src/components/mii/list/other-filters.tsx similarity index 100% rename from src/components/mii-list/other-filters.tsx rename to src/components/mii/list/other-filters.tsx diff --git a/src/components/mii-list/pagination.tsx b/src/components/mii/list/pagination.tsx similarity index 100% rename from src/components/mii-list/pagination.tsx rename to src/components/mii/list/pagination.tsx diff --git a/src/components/mii-list/platform-select.tsx b/src/components/mii/list/platform-select.tsx similarity index 100% rename from src/components/mii-list/platform-select.tsx rename to src/components/mii/list/platform-select.tsx diff --git a/src/components/mii-list/skeleton.tsx b/src/components/mii/list/skeleton.tsx similarity index 100% rename from src/components/mii-list/skeleton.tsx rename to src/components/mii/list/skeleton.tsx diff --git a/src/components/mii-list/sort-select.tsx b/src/components/mii/list/sort-select.tsx similarity index 100% rename from src/components/mii-list/sort-select.tsx rename to src/components/mii/list/sort-select.tsx diff --git a/src/components/mii-list/tag-filter.tsx b/src/components/mii/list/tag-filter.tsx similarity index 97% rename from src/components/mii-list/tag-filter.tsx rename to src/components/mii/list/tag-filter.tsx index 4cbbb8c..ad4b0d3 100644 --- a/src/components/mii-list/tag-filter.tsx +++ b/src/components/mii/list/tag-filter.tsx @@ -2,7 +2,7 @@ import { useRouter, useSearchParams } from "next/navigation"; import { useEffect, useMemo, useState, useTransition } from "react"; -import TagSelector from "../tag-selector"; +import TagSelector from "../../tag-selector"; interface Props { isExclude?: boolean; diff --git a/src/components/mii/personality-viewer.tsx b/src/components/mii/personality-viewer.tsx new file mode 100644 index 0000000..35eab7c --- /dev/null +++ b/src/components/mii/personality-viewer.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { SwitchMiiInstructions } from "@/types"; + +interface Props { + data: SwitchMiiInstructions["personality"]; + onClick?: (key: string, i: number) => void; +} + +const PERSONALITY_SETTINGS: { label: string; left: string; right: string }[] = [ + { label: "Movement", left: "Slow", right: "Quick" }, + { label: "Speech", left: "Polite", right: "Honest" }, + { label: "Energy", left: "Flat", right: "Varied" }, + { label: "Thinking", left: "Serious", right: "Chill" }, + { label: "Overall", left: "Normal", right: "Quirky" }, +]; + +export default function PersonalityViewer({ data, onClick }: Props) { + return ( +
+ {PERSONALITY_SETTINGS.map(({ label, left, right }) => { + const key = label.toLowerCase() as keyof typeof data; + return ( +
+ {label} + {left} +
+ {Array.from({ length: 6 }).map((_, i) => { + const colors = ["bg-green-400", "bg-green-300", "bg-teal-200", "bg-orange-200", "bg-orange-300", "bg-orange-400"]; + return ( + + ); + })} +
+ {right} +
+ ); + })} +
+ ); +} diff --git a/src/components/share-mii-button.tsx b/src/components/mii/share-mii-button.tsx similarity index 100% rename from src/components/share-mii-button.tsx rename to src/components/mii/share-mii-button.tsx diff --git a/src/components/mii/voice-viewer.tsx b/src/components/mii/voice-viewer.tsx new file mode 100644 index 0000000..c22a95e --- /dev/null +++ b/src/components/mii/voice-viewer.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { SwitchMiiInstructions } from "@/types"; +import { ChangeEvent } from "react"; + +interface Props { + data: SwitchMiiInstructions["voice"]; + onClick?: (e: ChangeEvent, label: string) => void; + onClickTone?: (i: number) => void; +} + +const VOICE_SETTINGS: string[] = ["Speed", "Pitch", "Depth", "Delivery"]; + +export default function VoiceViewer({ data, onClick, onClickTone }: Props) { + return ( +
+ {VOICE_SETTINGS.map((label) => ( +
+ + { + if (onClick) onClick(e, label); + }} + /> +
+ ))} + +
+ +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+
+
+ ); +} diff --git a/src/components/submit-form/mii-editor/color-picker.tsx b/src/components/submit-form/mii-editor/color-picker.tsx index 939fbfa..c0f009c 100644 --- a/src/components/submit-form/mii-editor/color-picker.tsx +++ b/src/components/submit-form/mii-editor/color-picker.tsx @@ -1,5 +1,6 @@ -import { Icon } from "@iconify/react"; import { useEffect, useState } from "react"; +import { Icon } from "@iconify/react"; +import { COLORS } from "@/lib/switch"; interface Props { disabled?: boolean; @@ -58,10 +59,7 @@ export default function ColorPicker({ disabled, color, setColor }: Props) { - ))} - - - + { + setVoice((p) => ({ ...p, [label]: e.target.valueAsNumber })); + instructions.current.voice[label as keyof typeof voice] = e.target.valueAsNumber; + }} + onClickTone={(i) => { + setVoice((p) => ({ ...p, tone: i })); + instructions.current.voice.tone = i; + }} + /> @@ -209,35 +129,13 @@ export default function HeadTab({ instructions }: Props) {
-
- {PERSONALITY_SETTINGS.map(({ label, left, right }) => { - const key = label.toLowerCase() as keyof typeof personality; - return ( -
- {label} - {left} -
- {Array.from({ length: 6 }).map((_, i) => { - const colors = ["bg-green-400", "bg-green-300", "bg-teal-200", "bg-orange-200", "bg-orange-300", "bg-orange-400"]; - return ( - - ); - })} -
- {right} -
- ); - })} -
+ { + setPersonality((p) => ({ ...p, [key]: i })); + instructions.current.personality = personality; + }} + /> diff --git a/src/lib/switch.ts b/src/lib/switch.ts new file mode 100644 index 0000000..647fadf --- /dev/null +++ b/src/lib/switch.ts @@ -0,0 +1,136 @@ +export const COLORS: string[] = [ + // Outside + "000000", + "8E8E93", + "6B4F0F", + "5A2A0A", + "7A1E0E", + "A0522D", + "A56B2A", + "D4A15A", + // Row 1 + "F2F2F2", + "E6D5C3", + "F3E6A2", + "CDE6A1", + "A9DFA3", + "8ED8B0", + "8FD3E8", + "C9C2E6", + "F3C1CF", + "F0A8A8", + // Row 2 + "D8D8D8", + "E8C07D", + "F0D97A", + "CDE07A", + "7BC96F", + "6BC4B2", + "5BBAD6", + "D9A7E0", + "F7B6C2", + "F47C6C", + // Row 3 + "C0C0C0", + "D9A441", + "F4C542", + "D4C86A", + "8FD14F", + "58B88A", + "6FA8DC", + "B4A7D6", + "F06277", + "FF6F61", + // Row 4 + "A8A8A8", + "D29B62", + "F2CF75", + "D8C47A", + "8DB600", + "66C2A5", + "4DA3D9", + "C27BA0", + "D35D6E", + "FF4C3B", + // Row 5 + "9A9A9A", + "C77800", + "F4B183", + "D6BF3A", + "3FA34D", + "4CA3A3", + "7EA6E0", + "B56576", + "FF1744", + "FF2A00", + // Row 6 + "8A817C", + "B85C1E", + "FF8C00", + "D2B48C", + "2E8B57", + "2F7E8C", + "2E86C1", + "7D5BA6", + "C2185B", + "E0193A", + // Row 7 + "6E6E6E", + "95543A", + "F4A460", + "B7A369", + "3B7A0A", + "1F6F78", + "3F51B5", + "673AB7", + "B71C1C", + "C91F3A", + // Row 8 + "3E3E3E", + "8B5A2B", + "F0986C", + "9E8F2A", + "0B5D3B", + "0E3A44", + "1F2A44", + "4B2E2E", + "9C1B1B", + "7A3B2E", + // Row 9 + "2E2E2E", + "7A4A2A", + "A86A1D", + "6E6B2A", + "2F6F55", + "004E52", + "1C2F6E", + "3A1F4D", + "A52A2A", + "8B4513", + // Row 10 + "000000", + "5A2E0C", + "7B3F00", + "5C4A00", + "004225", + "003B44", + "0A1F44", + "2B1B3F", + "7B2D2D", + "8B3A0E", + // Head tab extra colors + "FFD8BA", + "FFD5AC", + "FEC1A4", + "FEC68F", + "FEB089", + "FEBA6B", + "F39866", + "E89854", + "E37E3F", + "B45627", + "914220", + "59371F", + "662D16", + "392D1E", +];