Fixed inventory equipment, crafting tab scroll
Some checks are pending
Deploy MMO Server / deploy (push) Waiting to run

This commit is contained in:
MaksSlyzar 2026-05-05 01:37:32 +03:00
parent 2a266becea
commit 75897440c9
5 changed files with 170 additions and 150 deletions

View File

@ -36,7 +36,7 @@ class GameDataManager {
data.quests.forEach((q) => this.quests.set(q.id, q));
}
console.log(this.skills);
console.log(this.recipes);
if (data.languages) {
this.translations = data.languages;
}

View File

@ -50,7 +50,7 @@ const Navigation = ({ activeTab, onTabChange }) => {
if (id === "itemlist") return "ITEM_LIST";
if (id === "chat") return "CHAT";
if (id === "notifications") return "ALERTS";
return GameDataManager.t(`category.tabs.original.${id}`);
return GameDataManager.t(`category.tabs.core.${id}`);
};
return (

View File

@ -2,14 +2,12 @@ import React, { useState, useEffect } from "react";
import { useSocket } from "../../../hooks/useSocket";
import GameDataManager from "../../../services/GameDataManager";
import "./styles/CraftingTab.css";
import CategorySelector from "../components/CategorySelector";
import CraftModal from "./components/CraftModal";
import { config } from "../../../config/api";
import MeteorRegion from "../../../components/Meteor/MeteorRegion.jsx";
const CraftingTab = () => {
const { socket } = useSocket();
const [categories, setCategories] = useState([]);
const [recipes, setRecipes] = useState([]);
const [userInventory, setUserInventory] = useState([]);
@ -27,9 +25,7 @@ const CraftingTab = () => {
useEffect(() => {
if (activeCategory) {
const filteredRecipes =
GameDataManager.getRecipesByCategory(activeCategory);
setRecipes(filteredRecipes);
setRecipes(GameDataManager.getRecipesByCategory(activeCategory));
}
}, [activeCategory]);
@ -43,8 +39,7 @@ const CraftingTab = () => {
const handleCraftStarted = (data) => {
const recipeData = GameDataManager.getRecipe(data.recipeId);
const now = Date.now();
const diff = (data.finishAt - now) / 1000;
const diff = (data.finishAt - Date.now()) / 1000;
if (diff <= 0) {
setActiveCraft(null);
@ -59,9 +54,7 @@ const CraftingTab = () => {
timeLeft: Math.max(0, Math.ceil(diff)),
});
if (recipeData) {
setSelectedRecipe(recipeData);
}
if (recipeData) setSelectedRecipe(recipeData);
};
const handleCraftSuccess = () => {
@ -83,11 +76,11 @@ const CraftingTab = () => {
useEffect(() => {
if (!activeCraft) return;
const timer = setInterval(() => {
const now = Date.now();
const diff = Math.max(0, Math.ceil((activeCraft.finishAt - now) / 1000));
const diff = Math.max(
0,
Math.ceil((activeCraft.finishAt - Date.now()) / 1000),
);
if (diff <= 0) {
clearInterval(timer);
setActiveCraft(null);
@ -95,7 +88,6 @@ const CraftingTab = () => {
setActiveCraft((prev) => (prev ? { ...prev, timeLeft: diff } : null));
}
}, 1000);
return () => clearInterval(timer);
}, [activeCraft?.finishAt]);
@ -119,8 +111,7 @@ const CraftingTab = () => {
<div className="active-craft-panel">
<div className="craft-info">
<span>
<i className="fas fa-hammer fa-spin"></i> Assembling:{" "}
{activeCraft.name}
<i className="fas fa-hammer fa-spin"></i> {activeCraft.name}
</span>
<span className="time-left">{activeCraft.timeLeft}s</span>
</div>
@ -137,27 +128,32 @@ const CraftingTab = () => {
<div className="crafting-header">
<h2>
<i className="fas fa-microchip"></i> Fabrication Unit
<i className="fas fa-microchip"></i> Fabrication
</h2>
</div>
<CategorySelector
categories={categories}
activeCategory={activeCategory}
onCategoryChange={setActiveCategory}
/>
<div className="crafting-categories">
{categories.map((cat) => (
<button
key={cat.id}
className={`crafting-cat-btn ${activeCategory === cat.id ? "active" : ""}`}
onClick={() => setActiveCategory(cat.id)}
>
{cat.displayName || cat.id}
</button>
))}
</div>
<div className="crafting-grid">
{recipes.map((recipe) => {
const isThisRecipeCrafting = activeCraft?.recipeId === recipe.id;
const isCrafting = activeCraft?.recipeId === recipe.id;
const canCraft = recipe.ingredients.every(
(ing) => getOwnedAmount(ing.itemId) >= ing.quantity,
);
return (
<div
key={recipe.id}
className={`recipe-card ${!canCraft && !isThisRecipeCrafting ? "insufficient-resources" : ""} ${isThisRecipeCrafting ? "crafting-active" : ""}`}
className={`recipe-card ${!canCraft && !isCrafting ? "insufficient-resources" : ""} ${isCrafting ? "crafting-active" : ""}`}
onClick={() => setSelectedRecipe(recipe)}
>
<div className="recipe-icon">
@ -165,21 +161,17 @@ const CraftingTab = () => {
<img
width={64}
src={`${config.serverUrl}/static/${recipe.texture}`}
alt={recipe.displayName}
alt=""
/>
) : (
<div className="fallback-icon">{recipe.displayName[0]}</div>
)}
</div>
<div className="recipe-info-main">
<span className="recipe-name">{recipe.displayName}</span>
<div className="recipe-badges">
<span className="badge-time">
<i className="fas fa-clock"></i> {recipe.time_seconds}s
</span>
</div>
</div>
{isThisRecipeCrafting && (
<span className="recipe-name">{recipe.displayName}</span>
<span className="badge-time">
<i className="fas fa-clock"></i> {recipe.time_seconds}s
</span>
{isCrafting && (
<div className="craft-overlay-mini">
<i className="fas fa-sync fa-spin"></i>
</div>

View File

@ -19,7 +19,6 @@ const InventoryTab = () => {
const manifest = GameDataManager.manifest || {};
const coreSystems = manifest.core_systems?.categories || {};
const getFullTextureUrl = (path) => {
if (!path) return "/assets/no-image.png";
if (path.startsWith("http")) return path;
@ -89,36 +88,43 @@ const InventoryTab = () => {
const equipmentSlots = {
personal: Object.keys(coreSystems)
.filter((k) => {
const system = coreSystems[k];
return (
system.slotType === "personal" &&
!k.includes("accessory") &&
k !== "original:weapon"
);
})
.map((k) => ({
id: k,
label: GameDataManager.t(coreSystems[k].displayName),
})),
weapons: Object.keys(coreSystems)
.filter((k) => k === "original:weapon")
.map((k) => ({
id: k,
label: GameDataManager.t(coreSystems[k].displayName),
})),
accessories: Object.keys(coreSystems)
.filter(
(k) =>
k.startsWith("original:personal_") &&
!k.includes("accessory") &&
k !== "original:personal_weapons",
k.includes("accessory") && coreSystems[k].slotType === "personal",
)
.map((k) => ({
id: k,
label: GameDataManager.t(coreSystems[k].displayName),
})),
weapons: Object.keys(coreSystems)
.filter((k) => k === "original:personal_weapons")
.map((k) => ({
id: k,
label: GameDataManager.t(coreSystems[k].displayName),
})),
accessories: Object.keys(coreSystems)
.filter((k) => k.includes("personal_accessory"))
.map((k) => ({
id: k,
label: GameDataManager.t(coreSystems[k].displayName),
})),
ship: Object.keys(coreSystems)
.filter((k) => k.startsWith("original:ship_"))
.filter((k) => coreSystems[k].slotType === "ship")
.map((k) => ({
id: k,
label: GameDataManager.t(coreSystems[k].displayName),
})),
};
const renderSlotGroup = (title, groupSlots) => (
<div className="equip-group">
<div className="group-label">{title}</div>

View File

@ -3,83 +3,56 @@
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
.crafting-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
margin-bottom: 15px;
}
.crafting-header h2 {
font-size: 1.2rem;
}
.crafting-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 15px;
margin-top: 20px;
}
.recipe-card {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
padding: 15px;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.recipe-card:hover {
transform: translateY(-5px);
border-color: var(--primary-color);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.recipe-icon img {
max-width: 50px;
height: auto;
}
.recipe-name {
font-size: 0.85rem;
line-height: 1.2;
color: #fff;
font-weight: 500;
}
.badge-time {
font-size: 0.75rem;
color: var(--text-secondary);
margin: 0;
color: var(--primary-color);
text-transform: uppercase;
letter-spacing: 1px;
}
.crafting-categories {
display: flex;
flex-wrap: nowrap;
gap: 10px;
margin-bottom: 15px;
padding: 5px 0;
margin-bottom: 20px;
padding: 5px 5px 12px 5px;
overflow-x: auto;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
scrollbar-color: var(--primary-color) transparent;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.crafting-categories::-webkit-scrollbar {
display: none;
height: 3px;
}
.crafting-categories::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
}
.crafting-categories::-webkit-scrollbar-thumb {
background: var(--primary-color);
border-radius: 10px;
}
.crafting-cat-btn {
flex: 0 0 auto;
padding: 8px 16px;
padding: 10px 18px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border-color);
border-radius: 4px;
border-left: 3px solid var(--border-color);
color: var(--text-secondary);
font-family: "Orbitron", sans-serif;
font-size: 0.75rem;
@ -90,56 +63,105 @@
transition: all 0.2s ease;
}
.crafting-cat-btn.active {
background: var(--primary-color);
.crafting-cat-btn:hover {
background: rgba(255, 255, 255, 0.08);
border-color: var(--primary-color);
color: #000;
color: #fff;
}
.crafting-cat-btn.active {
background: linear-gradient(
90deg,
rgba(0, 243, 255, 0.15) 0%,
transparent 100%
);
border-color: var(--primary-color);
border-left: 3px solid var(--primary-color);
color: var(--primary-color);
font-weight: 700;
box-shadow: inset 5px 0 10px rgba(0, 243, 255, 0.05);
}
@media (max-width: 600px) {
.crafting-container {
padding: 12px;
}
.crafting-header {
margin-bottom: 12px;
}
.crafting-header h2 {
font-size: 1rem;
}
.crafting-grid {
grid-template-columns: repeat(2, 1fr);
gap: 8px;
margin-top: 10px;
}
.recipe-card {
padding: 10px;
gap: 6px;
}
.recipe-icon img {
max-width: 40px;
}
.recipe-name {
font-size: 0.75rem;
}
.crafting-cat-btn {
padding: 6px 12px;
font-size: 0.7rem;
}
.crafting-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 15px;
overflow-y: auto;
padding-right: 5px;
}
.tab-content.active {
height: 100%;
.crafting-grid::-webkit-scrollbar {
width: 4px;
}
.crafting-grid::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
}
.recipe-card {
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--border-color);
padding: 15px;
border-radius: 4px;
text-align: center;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
position: relative;
}
.recipe-card:hover {
background: rgba(255, 255, 255, 0.06);
border-color: var(--primary-color);
transform: translateY(-2px);
}
.recipe-card.insufficient-resources {
opacity: 0.6;
filter: grayscale(0.5);
}
.recipe-card.crafting-active {
border-color: var(--primary-color);
box-shadow: 0 0 15px rgba(0, 243, 255, 0.2);
}
.active-craft-panel {
background: rgba(0, 243, 255, 0.05);
border: 1px solid var(--primary-color);
padding: 12px;
margin-bottom: 20px;
border-radius: 4px;
}
.craft-info {
display: flex;
justify-content: space-between;
font-family: "Orbitron", sans-serif;
font-size: 0.8rem;
margin-bottom: 8px;
}
.progress-bar-bg {
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
overflow: hidden;
}
.meteor-region-content {
width: 100%;
.progress-bar-fill {
height: 100%;
background: var(--primary-color);
box-shadow: 0 0 10px var(--primary-color);
transition: width 1s linear;
}
@media (max-width: 600px) {
.crafting-grid {
grid-template-columns: repeat(2, 1fr);
}
}