mirror of
https://github.com/exogen/t2-model-skinner.git
synced 2026-01-19 19:24:44 +00:00
Add new skins section
This commit is contained in:
parent
3fe12cb2b6
commit
c2fd1e9395
|
|
@ -1,4 +1,3 @@
|
|||
import path from "path";
|
||||
import { globby } from "globby";
|
||||
import orderBy from "lodash.orderby";
|
||||
|
||||
|
|
@ -42,19 +41,9 @@ const vehicleModels = [
|
|||
const T2_SKINS_PATH = process.env.T2_SKINS_PATH || "../t2-skins";
|
||||
|
||||
export async function getSkinConfig() {
|
||||
const [defaultSkins, customSkins, customWeaponSkins] = await Promise.all([
|
||||
Promise.all(
|
||||
models.map((name) => globby(`./public/textures/*.${name}.png`))
|
||||
),
|
||||
Promise.all(
|
||||
models.map((name) => globby(`${T2_SKINS_PATH}/docs/skins/*.${name}.png`))
|
||||
),
|
||||
Promise.all(
|
||||
weaponModels.map((name) =>
|
||||
globby(`${T2_SKINS_PATH}/docs/skins/*/weapon_${name}.png`)
|
||||
)
|
||||
),
|
||||
]);
|
||||
const defaultSkins = await Promise.all(
|
||||
models.map((name) => globby(`./public/textures/*.${name}.png`))
|
||||
);
|
||||
|
||||
return {
|
||||
defaultSkins: models.reduce((skins, name, i) => {
|
||||
|
|
@ -67,29 +56,6 @@ export async function getSkinConfig() {
|
|||
);
|
||||
return skins;
|
||||
}, {}),
|
||||
customSkins: {
|
||||
...models.reduce((skins, name, i) => {
|
||||
skins[name] = orderBy(
|
||||
customSkins[i].map((name) =>
|
||||
name.replace(/(^.*\/|\.[lmh](male|female|bioderm)\.png$)/g, "")
|
||||
),
|
||||
[(name) => name.toLowerCase()],
|
||||
["asc"]
|
||||
);
|
||||
return skins;
|
||||
}, {}),
|
||||
...weaponModels.reduce((skins, name, i) => {
|
||||
skins[name] = orderBy(
|
||||
customWeaponSkins[i].map((name) => {
|
||||
const match = name.match(/\/([^/]+)\/weapon_\w+\.png$/);
|
||||
return match[1];
|
||||
}),
|
||||
[(name) => name.toLowerCase()],
|
||||
["asc"]
|
||||
);
|
||||
return skins;
|
||||
}, {}),
|
||||
},
|
||||
modelDefaults: {
|
||||
// Players
|
||||
lmale: "Blood Eagle",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/78e521c3-3739cc27b3254d35.js","static/chunks/95b64a6e-a0ff77d56afeed48.js","static/chunks/31664189-69d752d1129a4958.js","static/chunks/545f34e4-3e66c340444ca8b2.js","static/chunks/1bfc9850-b4ceccea4b74407c.js","static/chunks/d7eeaac4-d223ea230e13423c.js","static/chunks/f580fadb-2911e2fbf64aae5a.js","static/chunks/470-094a8f589946fc6b.js","static/chunks/pages/index-6d3dd4f1be9e3279.js"],"/_error":["static/chunks/pages/_error-54b9fcf45cb5bc62.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|
||||
self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/78e521c3-3739cc27b3254d35.js","static/chunks/95b64a6e-a0ff77d56afeed48.js","static/chunks/31664189-69d752d1129a4958.js","static/chunks/545f34e4-3e66c340444ca8b2.js","static/chunks/1bfc9850-b4ceccea4b74407c.js","static/chunks/d7eeaac4-d223ea230e13423c.js","static/chunks/f580fadb-2911e2fbf64aae5a.js","static/chunks/470-094a8f589946fc6b.js","static/chunks/pages/index-f57aecfcfef566e7.js"],"/_error":["static/chunks/pages/_error-54b9fcf45cb5bc62.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
docs/_next/static/chunks/pages/index-f57aecfcfef566e7.js
Normal file
2
docs/_next/static/chunks/pages/index-f57aecfcfef566e7.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,13 +1,15 @@
|
|||
import getConfig from "next/config";
|
||||
import useWarrior from "./useWarrior";
|
||||
import { AiTwotoneFolderOpen } from "react-icons/ai";
|
||||
import { useRef } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import useTools from "./useTools";
|
||||
import { detectFileType } from "./importUtils";
|
||||
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
const { defaultSkins, customSkins, modelDefaults, materials } =
|
||||
publicRuntimeConfig;
|
||||
const { defaultSkins, modelDefaults, materials } = publicRuntimeConfig;
|
||||
|
||||
const baseManifestPath = `https://exogen.github.io/t2-skins`;
|
||||
const defaultCustomSkins = {};
|
||||
|
||||
export default function WarriorSelector() {
|
||||
const {
|
||||
|
|
@ -26,8 +28,49 @@ export default function WarriorSelector() {
|
|||
const { selectedMaterialIndex, setSelectedMaterialIndex } = useTools();
|
||||
const materialDefs = materials[actualModel];
|
||||
const materialDef = materialDefs[selectedMaterialIndex];
|
||||
const [customSkins, setCustomSkins] =
|
||||
useState<Record<string, string[]>>(defaultCustomSkins);
|
||||
const [newSkins, setNewSkins] =
|
||||
useState<Record<string, string[]>>(defaultCustomSkins);
|
||||
const [selectedSkinSection, setSelectedSkinSection] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
let ignore = false;
|
||||
|
||||
const loadCustomSkins = async () => {
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(`${baseManifestPath}/skins.json`, { signal });
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
if (!ignore) {
|
||||
const json = await res.json();
|
||||
if (!ignore) {
|
||||
setCustomSkins(json.customSkins ?? {});
|
||||
setNewSkins(json.newSkins ?? {});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadCustomSkins();
|
||||
|
||||
return () => {
|
||||
ignore = true;
|
||||
controller.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
let skinSelectValue = selectedSkin ?? "";
|
||||
if (selectedSkin && selectedSkinSection) {
|
||||
skinSelectValue = `${selectedSkinSection}/${selectedSkin}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="Toolbar">
|
||||
<div className="Field">
|
||||
|
|
@ -46,10 +89,21 @@ export default function WarriorSelector() {
|
|||
throw new Error("No data-model-type found");
|
||||
}
|
||||
const newModelHasSkin =
|
||||
defaultSkins[newActualModel]?.includes(selectedSkin) ||
|
||||
customSkins[newActualModel]?.includes(selectedSkin) ||
|
||||
(selectedSkin &&
|
||||
(defaultSkins[newActualModel]?.includes(selectedSkin) ||
|
||||
customSkins[newActualModel]?.includes(selectedSkin))) ||
|
||||
false;
|
||||
// startTransition(() => {
|
||||
|
||||
let newModelHasSection = false;
|
||||
if (
|
||||
selectedSkin &&
|
||||
selectedSkinSection === "new" &&
|
||||
newModelHasSkin
|
||||
) {
|
||||
newModelHasSection =
|
||||
newSkins[newActualModel]?.includes(selectedSkin);
|
||||
}
|
||||
|
||||
setSelectedAnimation(null);
|
||||
setAnimationPaused(false);
|
||||
setSelectedModelType(modelType);
|
||||
|
|
@ -59,7 +113,9 @@ export default function WarriorSelector() {
|
|||
setSelectedSkin(modelDefaults[newActualModel] ?? null);
|
||||
setSelectedSkinType("default");
|
||||
}
|
||||
// });
|
||||
if (!newModelHasSection) {
|
||||
setSelectedSkinSection(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<optgroup label="Players" data-model-type="player">
|
||||
|
|
@ -104,15 +160,22 @@ export default function WarriorSelector() {
|
|||
<div className="Buttons">
|
||||
<select
|
||||
id="SkinSelect"
|
||||
value={selectedSkin ?? ""}
|
||||
value={skinSelectValue}
|
||||
onChange={(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);
|
||||
const skinParts = event.target.value.split("/");
|
||||
const selectedSkin = skinParts.slice(-1)[0] ?? null;
|
||||
setSelectedSkin(selectedSkin);
|
||||
setSelectedSkinType(skinType);
|
||||
if (skinParts.length > 1) {
|
||||
setSelectedSkinSection(skinParts[0]);
|
||||
} else {
|
||||
setSelectedSkinSection(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="">Select a skin…</option>
|
||||
|
|
@ -127,14 +190,31 @@ export default function WarriorSelector() {
|
|||
);
|
||||
})}
|
||||
</optgroup>
|
||||
{newSkins[actualModel]?.length ? (
|
||||
<optgroup label="New Skins ✨" data-skin-type="custom">
|
||||
{newSkins[actualModel]?.map((name: string) => {
|
||||
return (
|
||||
<option key={`new/${name}`} value={`new/${name}`}>
|
||||
{name} ✨
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</optgroup>
|
||||
) : null}
|
||||
<optgroup label="Custom Skins" data-skin-type="custom">
|
||||
{customSkins[actualModel]?.map((name: string) => {
|
||||
return (
|
||||
<option key={name} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
{customSkins === defaultCustomSkins ? (
|
||||
<option key="loading" value="">
|
||||
Loading…
|
||||
</option>
|
||||
) : (
|
||||
customSkins[actualModel]?.map((name: string) => {
|
||||
return (
|
||||
<option key={name} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</optgroup>
|
||||
</>
|
||||
) : null}
|
||||
|
|
@ -199,6 +279,7 @@ export default function WarriorSelector() {
|
|||
}
|
||||
});
|
||||
setSelectedSkin(null);
|
||||
setSelectedSkinSection(null);
|
||||
setSkinImageUrls({
|
||||
[materialDef.file ?? materialDef.name]: [imageUrl],
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue