mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-03-28 19:23:15 +00:00
238 lines
8.4 KiB
TypeScript
238 lines
8.4 KiB
TypeScript
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<SwitchMiiInstructions>;
|
|
}
|
|
|
|
interface SectionProps {
|
|
name: string;
|
|
instructions: Partial<SwitchMiiInstructions[keyof SwitchMiiInstructions]>;
|
|
children?: React.ReactNode;
|
|
isSubSection?: boolean;
|
|
}
|
|
|
|
const ORDINAL_SUFFIXES: Record<string, string> = {
|
|
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 (
|
|
<span className="flex items-center">
|
|
<div className="size-5 rounded mr-1.5" style={{ backgroundColor: `#${COLORS[color]}` }}></div>
|
|
Color menu on left, <GridPosition index={color} cols={1} />
|
|
</span>
|
|
);
|
|
}
|
|
if (color >= 108) {
|
|
return (
|
|
<span className="flex items-center">
|
|
<div className="size-5 rounded mr-1.5" style={{ backgroundColor: `#${COLORS[color]}` }}></div>
|
|
Outside color menu, <GridPosition index={color - 108} cols={2} />
|
|
</span>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<span className="flex items-center">
|
|
<div className="size-5 rounded mr-1.5" style={{ backgroundColor: `#${COLORS[color]}` }}></div>
|
|
Color menu on right, <GridPosition index={color - 8} cols={10} />
|
|
</span>
|
|
);
|
|
}
|
|
|
|
interface TableCellProps {
|
|
label: string;
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
function TableCell({ label, children }: TableCellProps) {
|
|
return (
|
|
<tr className={"border-b border-orange-300/50 last:border-0"}>
|
|
<td className={"py-0.5 pr-6 text-amber-700 font-semibold w-30 text-sm"}>{label}</td>
|
|
<td className={"py-0.5 text-amber-950"}>{children}</td>
|
|
</tr>
|
|
);
|
|
}
|
|
|
|
function Section({ name, instructions, children, isSubSection }: SectionProps) {
|
|
if (typeof instructions !== "object" || !instructions) return null;
|
|
|
|
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 (
|
|
<div className={`p-3 ${isSubSection ? "not-first:mt-2 pt-0!" : "border-l-4 border-amber-400 bg-amber-100/50 rounded-r-lg py-2.5"}`}>
|
|
<h3 className="font-semibold text-xl text-amber-800 mb-1">{name}</h3>
|
|
|
|
<table className="w-full">
|
|
<tbody>
|
|
{color && (
|
|
<TableCell label="Color">
|
|
<ColorPosition color={color} />
|
|
</TableCell>
|
|
)}
|
|
{height && <TableCell label="Height">{height}</TableCell>}
|
|
{distance && <TableCell label="Distance">{distance}</TableCell>}
|
|
{rotation && <TableCell label="Rotation">{rotation}</TableCell>}
|
|
{size && <TableCell label="Size">{size}</TableCell>}
|
|
{stretch && <TableCell label="Stretch">{stretch}</TableCell>}
|
|
|
|
{children}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
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 (
|
|
<div className="bg-amber-50 border-2 border-amber-500 rounded-2xl shadow-lg p-4 flex flex-col gap-3 max-h-96 overflow-y-auto">
|
|
<h2 className="text-xl font-semibold text-amber-700 flex items-center gap-2">
|
|
<Icon icon="fa7-solid:list" />
|
|
Instructions
|
|
</h2>
|
|
|
|
{head && <Section name="Head" instructions={head}></Section>}
|
|
{hair && (
|
|
<Section name="Hair" instructions={hair}>
|
|
{hair.subColor && (
|
|
<TableCell label="Sub Color">
|
|
<ColorPosition color={hair.subColor} />
|
|
</TableCell>
|
|
)}
|
|
{hair.subColor2 && (
|
|
<TableCell label="Sub Color (Back)">
|
|
<ColorPosition color={hair.subColor2} />
|
|
</TableCell>
|
|
)}
|
|
{hair.style && <TableCell label="Tying Style">{hair.style}</TableCell>}
|
|
{hair.isFlipped && <TableCell label="Flipped">{hair.isFlipped ? "Yes" : "No"}</TableCell>}
|
|
</Section>
|
|
)}
|
|
{eyebrows && <Section name="Eyebrows" instructions={eyebrows}></Section>}
|
|
{eyes && (
|
|
<Section name="Eyes" instructions={eyes}>
|
|
<Section isSubSection name="Main" instructions={eyes.main} />
|
|
<Section isSubSection name="Eyelashes Top" instructions={eyes.eyelashesTop} />
|
|
<Section isSubSection name="Eyelashes Bottom" instructions={eyes.eyelashesBottom} />
|
|
<Section isSubSection name="Eyelid Top" instructions={eyes.eyelidTop} />
|
|
<Section isSubSection name="Eyelid Bottom" instructions={eyes.eyelidBottom} />
|
|
<Section isSubSection name="Eyeliner" instructions={eyes.eyeliner} />
|
|
<Section isSubSection name="Pupil" instructions={eyes.pupil} />
|
|
</Section>
|
|
)}
|
|
{nose && <Section name="Nose" instructions={nose}></Section>}
|
|
{lips && (
|
|
<Section name="Lips" instructions={lips}>
|
|
{lips.hasLipstick && <TableCell label="Lipstick">{lips.hasLipstick ? "Yes" : "No"}</TableCell>}
|
|
</Section>
|
|
)}
|
|
{ears && <Section name="Ears" instructions={ears}></Section>}
|
|
{glasses && (
|
|
<Section name="Glasses" instructions={glasses}>
|
|
{glasses.ringColor && (
|
|
<TableCell label="Ring Color">
|
|
<ColorPosition color={glasses.ringColor} />
|
|
</TableCell>
|
|
)}
|
|
{glasses.shadesColor && (
|
|
<TableCell label="Shades Color">
|
|
<ColorPosition color={glasses.shadesColor} />
|
|
</TableCell>
|
|
)}
|
|
</Section>
|
|
)}
|
|
{other && (
|
|
<Section name="Other" instructions={other}>
|
|
<Section isSubSection name="Wrinkles 1" instructions={other.wrinkles1} />
|
|
<Section isSubSection name="Wrinkles 2" instructions={other.wrinkles2} />
|
|
<Section isSubSection name="Beard" instructions={other.beard} />
|
|
<Section isSubSection name="Moustache" instructions={other.moustache}>
|
|
{other.moustache && other.moustache.isFlipped && <TableCell label="Flipped">{other.moustache.isFlipped ? "Yes" : "No"}</TableCell>}
|
|
</Section>
|
|
<Section isSubSection name="Goatee" instructions={other.goatee} />
|
|
<Section isSubSection name="Mole" instructions={other.mole} />
|
|
<Section isSubSection name="Eye Shadow" instructions={other.eyeShadow} />
|
|
<Section isSubSection name="Blush" instructions={other.blush} />
|
|
</Section>
|
|
)}
|
|
|
|
{(height || weight || datingPreferences || voice || personality) && (
|
|
<div className="pl-3 text-sm border-l-4 border-amber-400 bg-amber-100/50 rounded-r-lg py-2.5 text-amber-950">
|
|
<h3 className="font-semibold text-xl text-amber-800 mb-1">Misc</h3>
|
|
|
|
{height && (
|
|
<div className="flex mb-1">
|
|
<label htmlFor="height" className="w-16">
|
|
Height
|
|
</label>
|
|
<input id="height" type="range" min={0} max={100} step={1} disabled value={height} />
|
|
</div>
|
|
)}
|
|
{weight && (
|
|
<div className="flex">
|
|
<label htmlFor="weight" className="w-16">
|
|
Weight
|
|
</label>
|
|
<input id="weight" type="range" min={0} max={100} step={1} disabled value={weight} />
|
|
</div>
|
|
)}
|
|
{datingPreferences && (
|
|
<div className="pl-2 not-nth-2:mt-4">
|
|
<h4 className="font-semibold text-xl text-amber-800 mb-1">Dating Preferences</h4>
|
|
<div className="w-min">
|
|
<DatingPreferencesViewer data={datingPreferences} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
{voice && (
|
|
<div className="pl-2 not-nth-2:mt-4">
|
|
<h4 className="font-semibold text-xl text-amber-800 mb-1">Voice</h4>
|
|
<div className="w-min">
|
|
<VoiceViewer data={voice} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
{personality && (
|
|
<div className="pl-2 not-nth-2:mt-4">
|
|
<h4 className="font-semibold text-xl text-amber-800 mb-1">Personality</h4>
|
|
<div className="w-min">
|
|
<PersonalityViewer data={personality} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|