From e0597f054fabb941cf7dee3ea5c512b6c336173d Mon Sep 17 00:00:00 2001 From: MaksSlyzar Date: Sat, 4 Apr 2026 00:09:35 +0300 Subject: [PATCH] Added DatapackTab. Removed ItemListTab. --- client/src/services/GameDataManager.js | 14 +- .../src/views/GameInterface/GameInterface.jsx | 4 +- .../GameInterface/components/Navigation.jsx | 2 +- .../views/GameInterface/tabs/DatapackTab.jsx | 127 ++++++++++++ .../views/GameInterface/tabs/ItemListTab.jsx | 159 --------------- .../tabs/components/DatapackDetailsModal.css | 93 +++++++++ .../tabs/components/DatapackDetailsModal.jsx | 85 ++++++++ .../GameInterface/tabs/styles/DatapackTab.css | 153 ++++++++++++++ .../GameInterface/tabs/styles/ItemListTab.css | 187 ------------------ 9 files changed, 468 insertions(+), 356 deletions(-) create mode 100644 client/src/views/GameInterface/tabs/DatapackTab.jsx delete mode 100644 client/src/views/GameInterface/tabs/ItemListTab.jsx create mode 100644 client/src/views/GameInterface/tabs/components/DatapackDetailsModal.css create mode 100644 client/src/views/GameInterface/tabs/components/DatapackDetailsModal.jsx create mode 100644 client/src/views/GameInterface/tabs/styles/DatapackTab.css delete mode 100644 client/src/views/GameInterface/tabs/styles/ItemListTab.css diff --git a/client/src/services/GameDataManager.js b/client/src/services/GameDataManager.js index 81d07b5..a7186c7 100644 --- a/client/src/services/GameDataManager.js +++ b/client/src/services/GameDataManager.js @@ -6,7 +6,7 @@ class GameDataManager { this.recipes = new Map(); this.skills = new Map(); this.dungeons = new Map(); - this.enemies = new Map(); + this.hostiles = new Map(); this.translations = {}; this.manifest = {}; this.currentLang = localStorage.getItem("selected_lang") || "en_US"; @@ -24,7 +24,7 @@ class GameDataManager { data.dungeons.forEach((d) => this.dungeons.set(d.id, d)); } if (Array.isArray(data.enemies)) { - data.enemies.forEach((e) => this.enemies.set(e.id, e)); + data.enemies.forEach((e) => this.hostiles.set(e.id, e)); } if (Array.isArray(data.skills)) { data.skills.forEach((s) => this.skills.set(s.id, s)); @@ -105,11 +105,11 @@ class GameDataManager { return clean; } - getEnemy(id) { + getHostile(id) { const cleanId = this._cleanId(id); - const enemy = this.enemies.get(cleanId); + const hostile = this.hostiles.get(cleanId); - if (!enemy) { + if (!hostile) { return { id: cleanId, displayName: `Unknown Entity (${cleanId})`, @@ -119,8 +119,8 @@ class GameDataManager { } return { - ...enemy, - displayName: this.t(enemy.displayName), + ...hostile, + displayName: this.t(hostile.displayName), }; } diff --git a/client/src/views/GameInterface/GameInterface.jsx b/client/src/views/GameInterface/GameInterface.jsx index a13f492..75e9e09 100644 --- a/client/src/views/GameInterface/GameInterface.jsx +++ b/client/src/views/GameInterface/GameInterface.jsx @@ -13,10 +13,10 @@ import QuestsTab from "./tabs/QuestsTab"; import ShopTab from "./tabs/ShopTab"; import CraftingTab from "./tabs/CraftingTab"; import ChatTab from "./tabs/ChatTab"; -import ItemListTab from "./tabs/ItemListTab.jsx"; import Notification from "./tabs/NotificationTab.jsx"; import DungeonScreen from "./components/DungeonScreen"; import { useSocket } from "../../hooks/useSocket.js"; +import DatapackTab from "./tabs/DatapackTab.jsx"; const GameInterface = ({ onExit }) => { const [activeTab, setActiveTab] = useState("dashboard"); @@ -78,7 +78,7 @@ const GameInterface = ({ onExit }) => { inventory: , shop: , crafting: , - itemlist: , + datapack: , chat: , notifications: , }; diff --git a/client/src/views/GameInterface/components/Navigation.jsx b/client/src/views/GameInterface/components/Navigation.jsx index 8a1863b..822edc8 100644 --- a/client/src/views/GameInterface/components/Navigation.jsx +++ b/client/src/views/GameInterface/components/Navigation.jsx @@ -14,7 +14,7 @@ const Navigation = ({ activeTab, onTabChange }) => { { id: "inventory", icon: "fa-archive" }, { id: "shop", icon: "fa-store" }, { id: "crafting", icon: "fa-hammer" }, - { id: "itemlist", icon: "fa-list-ul" }, + { id: "datapack", icon: "fa-list-ul" }, { id: "chat", icon: "fa-comments" }, { id: "notifications", icon: "fa-bell" }, ]; diff --git a/client/src/views/GameInterface/tabs/DatapackTab.jsx b/client/src/views/GameInterface/tabs/DatapackTab.jsx new file mode 100644 index 0000000..7803eb9 --- /dev/null +++ b/client/src/views/GameInterface/tabs/DatapackTab.jsx @@ -0,0 +1,127 @@ +import React, { useState, useEffect } from "react"; +import GameDataManager from "../../../services/GameDataManager"; +import MeteorRegion from "../../../components/Meteor/MeteorRegion.jsx"; +import { config } from "../../../config/api"; +import DatapackDetailsModal from "./components/DatapackDetailsModal"; +import "./styles/DatapackTab.css"; + +const DatapackTab = () => { + const [activeSection, setActiveSection] = useState("items"); + const [searchQuery, setSearchQuery] = useState(""); + const [displayList, setDisplayList] = useState([]); + const [selectedItem, setSelectedItem] = useState(null); + + const sections = [ + { id: "items", label: "Items", icon: "fa-box" }, + { id: "hostiles", label: "Hostiles", icon: "fa-biohazard" }, + { id: "dungeons", label: "Dungeons", icon: "fa-dungeon" }, + { id: "recipes", label: "Recipes", icon: "fa-scroll" }, + ]; + + useEffect(() => { + let data = []; + switch (activeSection) { + case "items": + data = Array.from(GameDataManager.items.values()).map((i) => + GameDataManager.getItem(i.id), + ); + break; + case "hostiles": + data = Array.from(GameDataManager.hostiles.values()).map((h) => + GameDataManager.getHostile(h.id), + ); + break; + case "dungeons": + data = Array.from(GameDataManager.dungeons.values()).map((d) => + GameDataManager.getDungeon(d.id), + ); + break; + case "recipes": + data = Array.from(GameDataManager.recipes.values()).map((r) => + GameDataManager.getRecipe(r.id), + ); + break; + default: + data = []; + } + + if (searchQuery) { + const query = searchQuery.toLowerCase(); + data = data.filter( + (item) => + item.displayName?.toLowerCase().includes(query) || + item.id?.toLowerCase().includes(query), + ); + } + setDisplayList(data); + }, [activeSection, searchQuery]); + + return ( +
+
+
+ {sections.map((s) => ( + + ))} +
+ +
+ + setSearchQuery(e.target.value)} + /> +
+
+ + +
+ {displayList.map((item) => ( +
+ setSelectedItem({ ...item, sectionType: activeSection }) + } + > +
+ {item.texture ? ( + + ) : ( +
+ {item.displayName?.[0] || "?"} +
+ )} +
+
+ {item.displayName} + {item.id} +
+
+ ))} +
+
+ + {selectedItem && ( + setSelectedItem(null)} + /> + )} +
+ ); +}; + +export default DatapackTab; diff --git a/client/src/views/GameInterface/tabs/ItemListTab.jsx b/client/src/views/GameInterface/tabs/ItemListTab.jsx deleted file mode 100644 index 56f0c51..0000000 --- a/client/src/views/GameInterface/tabs/ItemListTab.jsx +++ /dev/null @@ -1,159 +0,0 @@ -import React, { useState, useEffect } from "react"; -import GameDataManager from "../../../services/GameDataManager.js"; -import "./styles/ItemListTab.css"; -import { config } from "../../../config/api.js"; -import MeteorRegion from "../../../components/Meteor/MeteorRegion.jsx"; - -const ItemListTab = () => { - const [allItems, setAllItems] = useState([]); - const [filteredItems, setFilteredItems] = useState([]); - const [searchQuery, setSearchQuery] = useState(""); - const [selectedCategory, setSelectedCategory] = useState("all"); - const [selectedItem, setSelectedItem] = useState(null); - const [isMobile, setIsMobile] = useState(window.innerWidth <= 768); - - const ASSET_BASE_URL = `${config.serverUrl}/static/`; - - useEffect(() => { - const handleResize = () => setIsMobile(window.innerWidth <= 768); - window.addEventListener("resize", handleResize); - - const itemsArray = Array.from(GameDataManager.items.keys()).map((id) => - GameDataManager.getItem(id), - ); - setAllItems(itemsArray); - setFilteredItems(itemsArray); - - return () => window.removeEventListener("resize", handleResize); - }, []); - - useEffect(() => { - let result = allItems; - if (selectedCategory !== "all") { - result = result.filter( - (item) => item.meta?.category === selectedCategory, - ); - } - if (searchQuery) { - const q = searchQuery.toLowerCase(); - result = result.filter( - (item) => - item.displayName.toLowerCase().includes(q) || - item.id.toLowerCase().includes(q), - ); - } - setFilteredItems(result); - }, [searchQuery, selectedCategory, allItems]); - - const getFullTextureUrl = (path) => { - if (!path) return "/assets/no-image.png"; - return path.startsWith("http") ? path : `${ASSET_BASE_URL}${path}`; - }; - - const categories = [ - "all", - ...new Set(allItems.map((i) => i.meta?.category).filter(Boolean)), - ]; - - const renderInspector = () => ( -
isMobile && setSelectedItem(null)} - > - {selectedItem && ( -
e.stopPropagation()}> - {isMobile && ( - - )} - -
-
- -
-
-
{selectedItem.id}
-

{selectedItem.displayName}

- - {selectedItem.meta?.rarity?.toUpperCase()} - -
-
- - -
-
DATA_DESCRIPTION
-

{selectedItem.description}

-
- - {selectedItem.stats && - Object.keys(selectedItem.stats).length > 0 && ( -
-
PARAMETER_READOUT
-
- {Object.entries(selectedItem.stats).map(([k, v]) => ( -
- {GameDataManager.getStatName(k)} - +{v} -
- ))} -
-
- )} -
-
- )} -
- ); - - return ( -
-
-
- setSearchQuery(e.target.value)} - /> -
-
- {categories.map((cat) => ( - - ))} -
- - - {filteredItems.map((item) => ( -
setSelectedItem(item)} - > -
- -
-
-
{item.displayName}
-
{item.id}
-
-
- ))} -
-
- {(!isMobile || (isMobile && selectedItem)) && renderInspector()} -
- ); -}; - -export default ItemListTab; diff --git a/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.css b/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.css new file mode 100644 index 0000000..e2b364a --- /dev/null +++ b/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.css @@ -0,0 +1,93 @@ +.datapack-modal-content { + background: #0f1115; + border: 1px solid var(--border-color); + width: 90%; + max-width: 450px; + border-radius: 12px; + position: relative; + padding: 25px; + box-shadow: 0 20px 50px rgba(0, 0, 0, 0.8); + animation: modalSlideUp 0.3s ease-out; +} + +.modal-header { + display: flex; + align-items: center; + gap: 20px; + margin-bottom: 20px; + padding-bottom: 15px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.modal-icon-big { + width: 80px; + height: 80px; + background: rgba(0, 0, 0, 0.4); + border: 1px solid var(--border-color); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; +} + +.modal-icon-big img { + width: 60px; +} + +.modal-title-group h3 { + margin: 0; + font-family: "Orbitron", sans-serif; + color: var(--primary-color); + font-size: 1.3rem; +} + +.modal-raw-id { + font-size: 0.75rem; + color: var(--text-secondary); + opacity: 0.6; +} + +.details-description { + font-size: 0.9rem; + line-height: 1.5; + color: #ccc; + margin-bottom: 20px; + font-style: italic; +} + +.details-section h4 { + font-size: 0.8rem; + text-transform: uppercase; + color: var(--text-secondary); + letter-spacing: 1px; + margin-bottom: 10px; + border-left: 3px solid var(--primary-color); + padding-left: 10px; +} + +.stat-row { + display: flex; + justify-content: space-between; + padding: 6px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.03); +} + +.stat-label { + color: #888; + font-size: 0.85rem; +} +.stat-value { + color: #fff; + font-family: monospace; +} + +@keyframes modalSlideUp { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} diff --git a/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.jsx b/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.jsx new file mode 100644 index 0000000..879dd5a --- /dev/null +++ b/client/src/views/GameInterface/tabs/components/DatapackDetailsModal.jsx @@ -0,0 +1,85 @@ +import React from "react"; +import GameDataManager from "../../../../services/GameDataManager"; +import { config } from "../../../../config/api"; +import "./DatapackDetailsModal.css"; + +const DatapackDetailsModal = ({ data, onClose }) => { + if (!data) return null; + + const renderStats = () => { + if (!data.stats) return null; + return ( +
+ {Object.entries(data.stats).map(([key, value]) => ( +
+ + {GameDataManager.getStatName(key) || key}: + + {value} +
+ ))} +
+ ); + }; + + return ( +
+
e.stopPropagation()} + > + + +
+
+ {data.texture ? ( + + ) : ( +
{data.displayName?.[0]}
+ )} +
+
+

{data.displayName}

+ {data.id} +
+
+ +
+ {data.description && ( +

{data.description}

+ )} + +
+

Properties & Stats

+ {renderStats()} + + {data.sectionType === "hostiles" && data.level && ( +
+ Base Level: + {data.level} +
+ )} +
+ + {data.ingredients && ( +
+

Recipe Requirements

+
+ {data.ingredients.map((ing, idx) => ( +
+ {ing.displayName} + x{ing.quantity} +
+ ))} +
+
+ )} +
+
+
+ ); +}; + +export default DatapackDetailsModal; diff --git a/client/src/views/GameInterface/tabs/styles/DatapackTab.css b/client/src/views/GameInterface/tabs/styles/DatapackTab.css new file mode 100644 index 0000000..dc12b54 --- /dev/null +++ b/client/src/views/GameInterface/tabs/styles/DatapackTab.css @@ -0,0 +1,153 @@ +.datapack-tab-wrapper { + display: flex; + flex-direction: column; + height: 100%; +} + +.datapack-controls { + padding: 15px 20px; + background: rgba(0, 0, 0, 0.2); + border-bottom: 1px solid var(--border-color); +} + +.section-selector { + display: flex; + gap: 10px; + margin-bottom: 15px; + overflow-x: auto; + scrollbar-width: none; +} + +.section-btn { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 5px; + padding: 10px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 6px; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s; + min-width: 80px; +} + +.section-btn i { + font-size: 1.1rem; +} +.section-btn span { + font-size: 0.75rem; + font-family: "Orbitron", sans-serif; +} + +.section-btn.active { + background: rgba(var(--primary-rgb), 0.1); + border-color: var(--primary-color); + color: var(--primary-color); +} + +.search-bar { + position: relative; + width: 100%; +} + +.search-bar i { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: var(--text-secondary); +} + +.search-bar input { + width: 100%; + padding: 10px 10px 10px 35px; + background: #0a0a0c; + border: 1px solid var(--border-color); + border-radius: 4px; + color: #fff; + font-size: 0.9rem; +} + +.datapack-content { + flex: 1; + padding: 20px; +} + +.datapack-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 12px; + padding-bottom: 30px; +} + +.datapack-card { + display: flex; + align-items: center; + gap: 15px; + padding: 12px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; +} + +.card-icon { + width: 48px; + height: 48px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.3); + border-radius: 4px; +} + +.card-icon img { + max-width: 32px; +} + +.card-info { + display: flex; + flex-direction: column; + overflow: hidden; +} + +.card-name { + color: #fff; + font-size: 0.9rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.card-id { + font-size: 0.7rem; + color: var(--text-secondary); + font-family: monospace; +} + +.card-meta { + font-size: 0.7rem; + color: var(--primary-color); + margin-top: 4px; +} + +@media (max-width: 600px) { + .datapack-grid { + grid-template-columns: 1fr; + gap: 8px; + } + + .datapack-controls { + padding: 10px; + } + + .section-btn { + padding: 8px 5px; + } + + .section-btn span { + font-size: 0.65rem; + } +} diff --git a/client/src/views/GameInterface/tabs/styles/ItemListTab.css b/client/src/views/GameInterface/tabs/styles/ItemListTab.css deleted file mode 100644 index 4858264..0000000 --- a/client/src/views/GameInterface/tabs/styles/ItemListTab.css +++ /dev/null @@ -1,187 +0,0 @@ -.item-list-container { - display: flex; - height: calc(100vh - 120px); - background: rgba(10, 15, 25, 0.95); - color: #e0e6ed; - font-family: "Rajdhani", "Segoe UI", sans-serif; - border-top: 1px solid rgba(0, 255, 255, 0.1); - overflow: hidden; - position: relative; -} - -.item-list-sidebar { - width: 380px; - border-right: 1px solid rgba(0, 255, 255, 0.1); - display: flex; - flex-direction: column; - background: rgba(5, 10, 20, 0.5); -} - -.search-box { - padding: 20px; -} - -.search-box input { - width: 100%; - background: rgba(0, 0, 0, 0.4); - border: 1px solid rgba(0, 255, 255, 0.2); - padding: 12px 15px; - color: #00ffff; - text-transform: uppercase; - letter-spacing: 1px; - font-size: 14px; -} - -.category-filters { - display: flex; - flex-wrap: wrap; - gap: 8px; - padding: 0 20px 15px; -} - -.filter-btn { - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.1); - color: #aaa; - padding: 4px 10px; - font-size: 11px; - cursor: pointer; -} - -.filter-btn.active { - background: rgba(0, 255, 255, 0.1); - color: #00ffff; - border-color: #00ffff; -} - -.items-list-scroll { - flex: 1; - overflow-y: auto; - padding: 10px 20px; -} - -.item-row { - display: flex; - align-items: center; - padding: 10px; - margin-bottom: 8px; - background: rgba(255, 255, 255, 0.02); - border-left: 3px solid #9d9d9d; - cursor: pointer; -} - -.item-row.selected { - background: rgba(0, 255, 255, 0.08); -} - -.item-row-icon { - width: 44px; - height: 44px; - background: rgba(0, 0, 0, 0.5); - margin-right: 15px; - display: flex; - align-items: center; - justify-content: center; -} - -.item-row-icon img { - width: 32px; - height: 32px; - image-rendering: pixelated; -} - -/* Inspector Base */ -.item-inspector { - flex: 1; - padding: 40px; - overflow-y: auto; -} - -.inspector-header { - display: flex; - gap: 25px; - margin-bottom: 30px; -} - -.header-visual { - width: 100px; - height: 100px; - background: rgba(0, 0, 0, 0.4); - border: 1px solid rgba(0, 255, 255, 0.2); - display: flex; - align-items: center; - justify-content: center; -} - -.header-visual img { - width: 64px; - height: 64px; -} - -/* Mobile Modal Logic */ -@media (max-width: 768px) { - .item-list-sidebar { - width: 100%; - border: none; - } - - .item-inspector.mobile-modal { - display: flex; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 3000; - background: rgba(0, 0, 0, 0.85); - backdrop-filter: blur(6px); - align-items: center; - justify-content: center; - padding: 20px; - } - - .inspector-content { - background: #0d121d; - border: 1px solid #00ffff44; - width: 100%; - max-height: 120vh; - overflow-y: auto; - position: relative; - padding: 20px; - box-shadow: 0 0 30px rgba(0, 0, 0, 0.5); - } - - .close-inspector { - position: absolute; - top: 10px; - right: 15px; - background: none; - border: none; - color: #00ffff; - font-size: 30px; - cursor: pointer; - } - - .item-inspector:not(.mobile-modal) { - display: none; - } -} - -/* Rarity */ -.item-row.legendary { - border-left-color: #ff8000; -} -.item-row.epic { - border-left-color: #a335ee; -} -.item-row.rare { - border-left-color: #0070dd; -} - -@media screen and (max-width: 600px) { - .section-title { - font-size: 16px; - padding: 0; - margin: 10px; - } -}