From 1543af0ccf20bf33337fe98de3d35e02050ac2c4 Mon Sep 17 00:00:00 2001 From: AINDUSTRIES Date: Fri, 25 Sep 2026 00:36:08 +0200 Subject: [PATCH] Progress on implementation of skin selector --- app/components/agent.tsx | 5 +- app/components/float.tsx | 61 ++++++-- app/components/inventory.tsx | 10 +- app/components/skin_dialog.tsx | 253 ++++++++++++++++++++----------- app/components/skin_selector.tsx | 115 ++++++++++++++ app/components/skin_tile.tsx | 10 +- app/types/skin.ts | 2 + 7 files changed, 342 insertions(+), 114 deletions(-) create mode 100644 app/components/skin_selector.tsx diff --git a/app/components/agent.tsx b/app/components/agent.tsx index be585de..ced6d57 100644 --- a/app/components/agent.tsx +++ b/app/components/agent.tsx @@ -1,10 +1,11 @@ import { Surface } from "@heroui/react"; +import type { AgentInfo } from "~/types/skin"; -export function Agent({ agent_id }: { agent_id: string }) { +export function Agent({ agent }: { agent: AgentInfo }) { return ( Agent image diff --git a/app/components/float.tsx b/app/components/float.tsx index 4915dd1..93cefd9 100644 --- a/app/components/float.tsx +++ b/app/components/float.tsx @@ -1,31 +1,60 @@ -import { Slider, Label } from "@heroui/react"; -import { useEffect, useState } from "react"; +import { Slider, Label, Typography, Input } from "@heroui/react"; +import { + useEffect, + type ChangeEvent, + type Dispatch, + type SetStateAction, +} from "react"; +import { AgentInfo, WeaponInfo } from "~/types/skin"; export function Float({ - float, - minFloat = 0, - maxFloat = 1, + skin, + setSkin, acceptInput = false, + hasLabel = false, }: { - float: number; + skin: WeaponInfo; + setSkin: Dispatch>; minFloat?: number; maxFloat?: number; acceptInput?: boolean; + hasLabel?: boolean; }) { - const [value, setValue] = useState(float); - - useEffect(() => { - setValue(Math.max(minFloat, Math.min(maxFloat, float))); - }, [float, minFloat, maxFloat]); - const handleChange = (newValue: number | number[]) => { - const val = Array.isArray(newValue) ? newValue[0]: newValue; - setValue(Math.max(minFloat, Math.min(maxFloat, val))); + const val = Array.isArray(newValue) ? newValue[0] : newValue; + const newSkin = new WeaponInfo(skin); + newSkin.float = Math.max(skin.min_float, Math.min(skin.max_float, val)); + setSkin(newSkin); + }; + + const handleInputChange = (event: ChangeEvent) => { + const val = event.currentTarget.valueAsNumber; + const newSkin = new WeaponInfo(skin); + newSkin.float = Math.max(skin.min_float, Math.min(skin.max_float, val)); + setSkin(newSkin); }; return ( <> - + {hasLabel && ( +
+ Float + +
+ )} + diff --git a/app/components/inventory.tsx b/app/components/inventory.tsx index 5de6f30..36d7d07 100644 --- a/app/components/inventory.tsx +++ b/app/components/inventory.tsx @@ -137,23 +137,23 @@ export function Inventory({ side }: { side: "T" | "CT" }) { className="flex flex-col rounded-xl w-1/3 p-4 gap-2 min-h-0" variant="secondary" > - {agent && } + {agent && }
{knife && ( )} {gloves && ( )} {agent && ( )} @@ -167,7 +167,7 @@ export function Inventory({ side }: { side: "T" | "CT" }) { {weapons.map((v, i) => ( ))} diff --git a/app/components/skin_dialog.tsx b/app/components/skin_dialog.tsx index 19417ca..04d088b 100644 --- a/app/components/skin_dialog.tsx +++ b/app/components/skin_dialog.tsx @@ -1,107 +1,186 @@ -import { Button, Modal, Surface, Typography } from "@heroui/react"; -import type { Dispatch, SetStateAction } from "react"; +import { Button, Input, Modal, Surface, Typography } from "@heroui/react"; +import { + useEffect, + useState, + type ChangeEvent, + type Dispatch, + type SetStateAction, +} from "react"; import { AgentInfo, WeaponInfo } from "~/types/skin"; import { Float } from "./float"; +import { SkinSelector } from "./skin_selector"; export function SkinDialog({ isOpen, setIsOpen, skin, - side + setSkin, + side, }: { isOpen: boolean; setIsOpen: Dispatch>; skin: WeaponInfo | AgentInfo; - side: "T" | "CT" + setSkin: Dispatch>; + side: "T" | "CT"; }) { const acolor = `${skin.color}64`; + const [selectorOpen, setSelectorOpen] = useState(false); + const [canClose, setCanClose] = useState(true); + const [localSkin, setLocalSkin] = useState(skin); + + const handleInputChange = (event: ChangeEvent) => { + if (localSkin instanceof AgentInfo) return; + const newSkin = new WeaponInfo(localSkin); + newSkin.pattern = event.currentTarget.valueAsNumber; + setLocalSkin(newSkin); + }; + + const applySkin = (side: "SIDE" | "BOTH") => { + setSkin(localSkin); + } + + useEffect(() => { + if (!selectorOpen) { + setTimeout(() => { + setCanClose(true); + }, 100); + } + }, [selectorOpen]); + + useEffect(() => { + if (isOpen) { + setLocalSkin(skin); + } + }, [isOpen, skin]); + return ( - - { - setIsOpen(false); - return true; - }} - > - - - { - setIsOpen(false); - }} - /> - -
- - {skin.display} - - + {localSkin instanceof WeaponInfo && ( + + )} + {localSkin instanceof WeaponInfo && ( +
+ + Pattern:{" "} + + +
+ )} + + Transaction id: {localSkin.id} + +
+ {localSkin instanceof WeaponInfo && ( + + )} +
- - {skin instanceof WeaponInfo && ( - <> - - Float: {skin.float} - - - - )} - {skin instanceof WeaponInfo && ( - - Pattern: {skin.pattern} - - )} - - Transaction id: {skin.id} - -
- {skin instanceof WeaponInfo && } -
-
-
-
-
-
-
+ + + + + + + ); } diff --git a/app/components/skin_selector.tsx b/app/components/skin_selector.tsx new file mode 100644 index 0000000..cd183b8 --- /dev/null +++ b/app/components/skin_selector.tsx @@ -0,0 +1,115 @@ +import { Button, Modal, Typography } from "@heroui/react"; +import { useEffect, useState, type Dispatch, type SetStateAction } from "react"; +import { useNotification } from "~/hooks/notification"; +import { AgentInfo, WeaponInfo } from "~/types/skin"; + +interface Info { + name: string; + paint_index: number; + display: string; + color: string; + min_float: number; + max_float: number; +} + +export function SkinSelector({ + isOpen, + setIsOpen, + skin, + setSkin, + side +}: { + isOpen: boolean; + setIsOpen: Dispatch>; + skin: WeaponInfo | AgentInfo; + setSkin: Dispatch>; + side: "T" | "CT" | "BOTH"; +}) { + const [skins, setSkins] = useState>([]); + const { notify } = useNotification(); + + const setPaint = (v: Info) => { + if (skin instanceof WeaponInfo) { + const newSkin = new WeaponInfo(skin); + newSkin.paint_index = v.paint_index; + newSkin.display = newSkin.display.split("|")[0] + " | " + v.display; + newSkin.min_float = v.min_float; + newSkin.max_float = v.max_float; + setSkin(newSkin); + } else { + const newSkin = new AgentInfo(skin); + newSkin.name = v.name; + setSkin(newSkin); + } + }; + + useEffect(() => { + const fetchSkins = async () => { + let response = await fetch( + `${import.meta.env.VITE_BASE_API_URL}/index?weapon=${skin.weapon_id}&team=${side}${skin instanceof AgentInfo ? "&agent=true" : ""}`, + { credentials: "include" }, + ) + .then((r) => r.json()) + .catch((e) => { + notify( + { + type: "danger", + loading: false, + title: "An error occured.", + message: `An error occured: ${e}`, + }, + 10000, + ); + }); + + setSkins(response); + }; + if (skins.length === 0 && isOpen) { + fetchSkins(); + } + }, [isOpen]); + return ( + + { + setIsOpen(false); + return true; + }} + > + + + { + setIsOpen(false); + }} + /> + +
+ {skins && + skins.map((v, i) => ( + + ))} +
+
+
+
+
+
+ ); +} diff --git a/app/components/skin_tile.tsx b/app/components/skin_tile.tsx index 7172892..d09bef1 100644 --- a/app/components/skin_tile.tsx +++ b/app/components/skin_tile.tsx @@ -5,20 +5,22 @@ import { SkinDialog } from "./skin_dialog"; import { WeaponInfo, AgentInfo } from "~/types/skin"; export function SkinTile({ - skin, + skinInfo, side, }: { - skin: WeaponInfo | AgentInfo; + skinInfo: WeaponInfo | AgentInfo; side: "T" | "CT"; }) { const [isDialogOpen, setIsDialogOpen] = useState(false); - const acolor = `${skin.color}64`; + const acolor = `${skinInfo.color}64`; + const [skin, setSkin] = useState(skinInfo); return ( <>