mirror of
https://github.com/exogen/t2-model-skinner.git
synced 2026-01-19 19:24:44 +00:00
191 lines
7.2 KiB
TypeScript
191 lines
7.2 KiB
TypeScript
import getConfig from "next/config";
|
|
import useWarrior from "./useWarrior";
|
|
import { AiTwotoneFolderOpen } from "react-icons/ai";
|
|
import { useRef } from "react";
|
|
import useTools from "./useTools";
|
|
|
|
const { publicRuntimeConfig } = getConfig();
|
|
const { defaultSkins, customSkins, modelDefaults, materials } =
|
|
publicRuntimeConfig;
|
|
|
|
export default function WarriorSelector() {
|
|
const {
|
|
selectedModel,
|
|
setSelectedModel,
|
|
selectedModelType,
|
|
setSelectedModelType,
|
|
selectedSkin,
|
|
setSelectedSkin,
|
|
setSelectedSkinType,
|
|
actualModel,
|
|
setSelectedAnimation,
|
|
setSkinImageUrls,
|
|
setAnimationPaused,
|
|
} = useWarrior();
|
|
const { selectedMaterialIndex, setSelectedMaterialIndex } = useTools();
|
|
const materialDefs = materials[actualModel];
|
|
const materialDef = materialDefs[selectedMaterialIndex];
|
|
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
|
|
return (
|
|
<div className="Toolbar">
|
|
<div className="Field">
|
|
<label htmlFor="ModelSelect">Model</label>
|
|
<select
|
|
id="ModelSelect"
|
|
value={selectedModel}
|
|
onChange={(event) => {
|
|
const parentNode = event.target.selectedOptions[0]
|
|
.parentNode as HTMLElement;
|
|
const newSelectedModel = event.target.value;
|
|
const { modelType } = parentNode.dataset;
|
|
if (!modelType) {
|
|
throw new Error("No data-model-type found");
|
|
}
|
|
const newModelHasSkin =
|
|
defaultSkins[newSelectedModel]?.includes(selectedSkin) ||
|
|
customSkins[newSelectedModel]?.includes(selectedSkin) ||
|
|
false;
|
|
// startTransition(() => {
|
|
setSelectedAnimation(null);
|
|
setAnimationPaused(false);
|
|
setSelectedModelType(modelType);
|
|
setSelectedModel(newSelectedModel);
|
|
setSelectedMaterialIndex(0);
|
|
if (!newModelHasSkin) {
|
|
setSelectedSkin(modelDefaults[newSelectedModel] ?? null);
|
|
setSelectedSkinType("default");
|
|
}
|
|
// });
|
|
}}
|
|
>
|
|
<optgroup label="Players" data-model-type="player">
|
|
<option value="lmale">Human Male • Light</option>
|
|
<option value="mmale">Human Male • Medium</option>
|
|
<option value="hmale">Human Male • Heavy</option>
|
|
<option value="lfemale">Human Female • Light</option>
|
|
<option value="mfemale">Human Female • Medium</option>
|
|
<option value="hfemale">Human Female • Heavy</option>
|
|
<option value="lbioderm">Bioderm • Light</option>
|
|
<option value="mbioderm">Bioderm • Medium</option>
|
|
<option value="hbioderm">Bioderm • Heavy</option>
|
|
</optgroup>
|
|
<optgroup label="Weapons" data-model-type="weapon">
|
|
<option value="disc">Disc Launcher</option>
|
|
<option value="chaingun">Chaingun</option>
|
|
<option value="grenade_launcher">Grenade Launcher</option>
|
|
<option value="sniper">Laser Rifle</option>
|
|
{/* <option value="plasmathrower">Plasma Cannon</option> */}
|
|
<option value="energy">Blaster</option>
|
|
<option value="shocklance">Shocklance</option>
|
|
<option value="elf">ELF Projector</option>
|
|
<option value="missile">Missile Launcher</option>
|
|
<option value="mortar">Mortar</option>
|
|
<option value="repair">Repair Pack</option>
|
|
<option value="targeting">Targeting Laser</option>
|
|
</optgroup>
|
|
<optgroup label="Vehicles" data-model-type="vehicle">
|
|
<option value="vehicle_air_scout">Shrike</option>
|
|
<option value="vehicle_land_mpbbase">MPB</option>
|
|
</optgroup>
|
|
</select>
|
|
</div>
|
|
<div className="Field">
|
|
<label htmlFor="SkinSelect">Skin</label>
|
|
<div className="Buttons">
|
|
<select
|
|
id="SkinSelect"
|
|
value={selectedSkin ?? ""}
|
|
onChange={async (event) => {
|
|
const parentNode = event.target.selectedOptions[0]
|
|
.parentNode as HTMLElement;
|
|
const skinType = event.target.value
|
|
? parentNode.dataset.skinType ?? null
|
|
: null;
|
|
setSelectedSkin(event.target.value || null);
|
|
setSelectedSkinType(skinType);
|
|
}}
|
|
>
|
|
<option value="">Select a skin…</option>
|
|
{selectedModelType === "player" ? (
|
|
<>
|
|
<optgroup label="Default Skins" data-skin-type="default">
|
|
{defaultSkins[actualModel]?.map((name: string) => {
|
|
return (
|
|
<option key={name} value={name}>
|
|
{name}
|
|
</option>
|
|
);
|
|
})}
|
|
</optgroup>
|
|
<optgroup label="Custom Skins" data-skin-type="custom">
|
|
{customSkins[actualModel]?.map((name: string) => {
|
|
return (
|
|
<option key={name} value={name}>
|
|
{name}
|
|
</option>
|
|
);
|
|
})}
|
|
</optgroup>
|
|
</>
|
|
) : null}
|
|
{selectedModelType === "weapon" ||
|
|
selectedModelType === "vehicle" ? (
|
|
<>
|
|
{modelDefaults[actualModel] ? (
|
|
<optgroup label="Default Skins" data-skin-type="default">
|
|
<option value={modelDefaults[actualModel]}>Default</option>
|
|
</optgroup>
|
|
) : null}
|
|
{customSkins[actualModel]?.length ? (
|
|
<optgroup label="Custom Skins" data-skin-type="custom">
|
|
{customSkins[actualModel].map((name: string) => (
|
|
<option key={name} value={name}>
|
|
{name}
|
|
</option>
|
|
))}
|
|
</optgroup>
|
|
) : null}
|
|
</>
|
|
) : null}
|
|
</select>
|
|
<button
|
|
type="button"
|
|
aria-label="Load Skin"
|
|
title="Load a Skin"
|
|
onClick={() => {
|
|
if (fileInputRef.current) {
|
|
fileInputRef.current.click();
|
|
}
|
|
}}
|
|
>
|
|
<AiTwotoneFolderOpen style={{ fontSize: 18 }} />
|
|
</button>
|
|
<input
|
|
ref={fileInputRef}
|
|
onChange={async (event) => {
|
|
const imageUrl = await new Promise<string>((resolve, reject) => {
|
|
const inputFile = event.target.files?.[0];
|
|
if (inputFile) {
|
|
const reader = new FileReader();
|
|
reader.addEventListener("load", (event) => {
|
|
resolve(event.target?.result as string);
|
|
});
|
|
reader.readAsDataURL(inputFile);
|
|
} else {
|
|
reject(new Error("No input file provided."));
|
|
}
|
|
});
|
|
setSelectedSkin(null);
|
|
setSkinImageUrls({ [materialDef.name]: imageUrl });
|
|
}}
|
|
type="file"
|
|
accept=".png, image/png"
|
|
hidden
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|