814 lines
25 KiB
TypeScript
814 lines
25 KiB
TypeScript
import { useEffect, useState } from 'react';
|
||
import { Box, Typography, Grid, Button, Paper, FormControl, Select, MenuItem, InputLabel } from '@mui/material';
|
||
|
||
import { FullScreenLoader } from '../components/FullScreenLoader';
|
||
import { translateServer } from '../utils/serverTranslator';
|
||
import {
|
||
fetchInventoryItems,
|
||
withdrawInventoryItem,
|
||
type InventoryRawItem,
|
||
type InventoryItemsResponse,
|
||
} from '../api';
|
||
import CustomTooltip from '../components/Notifications/CustomTooltip';
|
||
import { getPlayerServer } from '../utils/playerOnlineCheck';
|
||
|
||
const KNOWN_SERVER_IPS = [
|
||
'minecraft.hub.popa-popa.ru',
|
||
'minecraft.survival.popa-popa.ru',
|
||
'minecraft.minigames.popa-popa.ru',
|
||
];
|
||
|
||
const STORAGE_KEY = 'inventory_layout';
|
||
|
||
function stripMinecraftColors(text?: string | null): string {
|
||
if (!text) return '';
|
||
return text.replace(/§[0-9A-FK-ORa-fk-or]/g, '');
|
||
}
|
||
|
||
const CARD_BG =
|
||
'radial-gradient(circle at 10% 10%, rgba(242,113,33,0.14), transparent 55%), radial-gradient(circle at 90% 20%, rgba(233,64,205,0.12), transparent 55%), rgba(10,10,20,0.86)';
|
||
|
||
const CardFacePaperSx = {
|
||
borderRadius: '1.2vw',
|
||
background: CARD_BG,
|
||
border: '1px solid rgba(255,255,255,0.08)',
|
||
boxShadow: '0 1.2vw 3.2vw rgba(0,0,0,0.55)',
|
||
color: 'white',
|
||
} as const;
|
||
|
||
function readInventoryLayout(): any {
|
||
try {
|
||
const raw = localStorage.getItem(STORAGE_KEY);
|
||
return raw ? JSON.parse(raw) : {};
|
||
} catch {
|
||
return {};
|
||
}
|
||
}
|
||
|
||
function writeInventoryLayout(next: any) {
|
||
localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
||
}
|
||
|
||
function getLayout(username: string, serverIp: string): Record<string, number> {
|
||
const inv = readInventoryLayout();
|
||
return inv?.[username]?.[serverIp] ?? {};
|
||
}
|
||
|
||
function setLayout(username: string, serverIp: string, layout: Record<string, number>) {
|
||
const inv = readInventoryLayout();
|
||
inv[username] ??= {};
|
||
inv[username][serverIp] = layout;
|
||
writeInventoryLayout(inv);
|
||
}
|
||
|
||
function buildSlots(
|
||
items: InventoryRawItem[],
|
||
layout: Record<string, number>,
|
||
size = 28,
|
||
): (InventoryRawItem | null)[] {
|
||
const slots: (InventoryRawItem | null)[] = Array.from({ length: size }, () => null);
|
||
|
||
const byId = new Map(items.map((it) => [it.id, it]));
|
||
const used = new Set<string>();
|
||
|
||
// 1) ставим туда, куда сохранено
|
||
for (const [id, idx] of Object.entries(layout)) {
|
||
const i = Number(idx);
|
||
const it = byId.get(id);
|
||
if (!it) continue;
|
||
if (Number.isFinite(i) && i >= 0 && i < size && !slots[i]) {
|
||
slots[i] = it;
|
||
used.add(id);
|
||
}
|
||
}
|
||
|
||
// 2) остальные — в первые пустые
|
||
for (const it of items) {
|
||
if (used.has(it.id)) continue;
|
||
const empty = slots.findIndex((x) => x === null);
|
||
if (empty === -1) break;
|
||
slots[empty] = it;
|
||
used.add(it.id);
|
||
}
|
||
|
||
return slots;
|
||
}
|
||
|
||
export default function Inventory() {
|
||
const [username, setUsername] = useState('');
|
||
const [loading, setLoading] = useState(true);
|
||
const [availableServers, setAvailableServers] = useState<string[]>([]);
|
||
const [selectedServerIp, setSelectedServerIp] = useState<string>('');
|
||
const [items, setItems] = useState<InventoryRawItem[]>([]);
|
||
const [page, setPage] = useState(1);
|
||
const limit = 28;
|
||
const [total, setTotal] = useState(0);
|
||
const [pages, setPages] = useState(1);
|
||
const [withdrawingIds, setWithdrawingIds] = useState<string[]>([]);
|
||
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
||
|
||
const [isOnline, setIsOnline] = useState(false);
|
||
const [playerServer, setPlayerServer] = useState<string | null>(null);
|
||
const [checkingOnline, setCheckingOnline] = useState(false);
|
||
|
||
const [draggedItemId, setDraggedItemId] = useState<string | null>(null);
|
||
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
|
||
const [dragPos, setDragPos] = useState<{ x: number; y: number } | null>(null);
|
||
|
||
type SlotItem = InventoryRawItem | null;
|
||
|
||
const [slots, setSlots] = useState<SlotItem[]>(() => Array.from({ length: 28 }, () => null));
|
||
const [draggedFromIndex, setDraggedFromIndex] = useState<number | null>(null);
|
||
|
||
useEffect(() => {
|
||
const handleMove = (e: MouseEvent) => {
|
||
if (!draggedItemId) return;
|
||
setDragPos({ x: e.clientX, y: e.clientY });
|
||
};
|
||
|
||
const handleUp = () => {
|
||
if (
|
||
draggedItemId &&
|
||
draggedFromIndex !== null &&
|
||
dragOverIndex !== null &&
|
||
draggedFromIndex !== dragOverIndex
|
||
) {
|
||
setSlots((prev) => {
|
||
const next = [...prev];
|
||
const moving = next[draggedFromIndex];
|
||
if (!moving) return prev;
|
||
|
||
// ✅ swap или move в пустоту
|
||
const target = next[dragOverIndex];
|
||
next[dragOverIndex] = moving;
|
||
next[draggedFromIndex] = target ?? null;
|
||
|
||
// ✅ сохраняем layout (позиции предметов)
|
||
const layout: Record<string, number> = {};
|
||
next.forEach((it, idx) => {
|
||
if (it) layout[it.id] = idx;
|
||
});
|
||
|
||
if (username && selectedServerIp) {
|
||
setLayout(username, selectedServerIp, layout);
|
||
}
|
||
|
||
return next;
|
||
});
|
||
}
|
||
|
||
setDraggedItemId(null);
|
||
setDraggedFromIndex(null);
|
||
setDragOverIndex(null);
|
||
setDragPos(null);
|
||
};
|
||
|
||
window.addEventListener('mousemove', handleMove);
|
||
window.addEventListener('mouseup', handleUp);
|
||
|
||
return () => {
|
||
window.removeEventListener('mousemove', handleMove);
|
||
window.removeEventListener('mouseup', handleUp);
|
||
};
|
||
}, [draggedItemId, dragOverIndex]);
|
||
|
||
useEffect(() => {
|
||
const savedConfig = localStorage.getItem('launcher_config');
|
||
if (!savedConfig) {
|
||
setLoading(false);
|
||
return;
|
||
}
|
||
|
||
const config = JSON.parse(savedConfig);
|
||
if (config?.username) setUsername(config.username);
|
||
setLoading(false);
|
||
}, []);
|
||
|
||
const detectServersWithItems = async (u: string) => {
|
||
const checks = await Promise.allSettled(
|
||
KNOWN_SERVER_IPS.map(async (ip) => {
|
||
const res = await fetchInventoryItems(u, ip, 1, 1);
|
||
return { ip, has: (res.items || []).length > 0 || (res.total ?? 0) > 0 };
|
||
}),
|
||
);
|
||
|
||
return checks
|
||
.filter(
|
||
(r): r is PromiseFulfilledResult<{ ip: string; has: boolean }> => r.status === 'fulfilled',
|
||
)
|
||
.filter((r) => r.value.has)
|
||
.map((r) => r.value.ip);
|
||
};
|
||
|
||
const loadInventory = async (u: string, ip: string, p: number) => {
|
||
const res: InventoryItemsResponse = await fetchInventoryItems(u, ip, p, limit);
|
||
const list = res.items || [];
|
||
|
||
setItems(res.items || []);
|
||
setTotal(res.total ?? 0);
|
||
setPages(Math.max(1, Math.ceil((res.total ?? 0) / (res.limit ?? limit))));
|
||
|
||
const layout = getLayout(u, ip);
|
||
setSlots(buildSlots(list, layout, 28));
|
||
};
|
||
|
||
useEffect(() => {
|
||
if (!username) return;
|
||
|
||
let cancelled = false;
|
||
|
||
(async () => {
|
||
try {
|
||
setLoading(true);
|
||
|
||
const servers = await detectServersWithItems(username);
|
||
if (cancelled) return;
|
||
|
||
setAvailableServers(servers);
|
||
|
||
const defaultIp = servers[0] || '';
|
||
setSelectedServerIp(defaultIp);
|
||
setPage(1);
|
||
|
||
if (defaultIp) {
|
||
await loadInventory(username, defaultIp, 1);
|
||
} else {
|
||
setItems([]);
|
||
setTotal(0);
|
||
setPages(1);
|
||
}
|
||
} catch (e) {
|
||
console.error(e);
|
||
setAvailableServers([]);
|
||
setSelectedServerIp('');
|
||
setItems([]);
|
||
setTotal(0);
|
||
setPages(1);
|
||
} finally {
|
||
if (!cancelled) setLoading(false);
|
||
}
|
||
})();
|
||
|
||
return () => {
|
||
cancelled = true;
|
||
};
|
||
}, [username]);
|
||
|
||
useEffect(() => {
|
||
if (!username || !selectedServerIp) return;
|
||
|
||
let cancelled = false;
|
||
|
||
(async () => {
|
||
try {
|
||
setLoading(true);
|
||
setPage(1);
|
||
await loadInventory(username, selectedServerIp, 1);
|
||
} catch (e) {
|
||
console.error(e);
|
||
setItems([]);
|
||
setTotal(0);
|
||
setPages(1);
|
||
} finally {
|
||
if (!cancelled) setLoading(false);
|
||
}
|
||
})();
|
||
|
||
return () => {
|
||
cancelled = true;
|
||
};
|
||
}, [selectedServerIp]);
|
||
|
||
const withWithdrawing = async (id: string, fn: () => Promise<void>) => {
|
||
setWithdrawingIds((prev) => [...prev, id]);
|
||
try {
|
||
await fn();
|
||
} finally {
|
||
setWithdrawingIds((prev) => prev.filter((x) => x !== id));
|
||
}
|
||
};
|
||
|
||
const handleWithdraw = async (item: InventoryRawItem) => {
|
||
if (!username || !selectedServerIp) return;
|
||
|
||
// сервер в UI может не совпадать — оставим защиту
|
||
if (selectedServerIp !== item.server_ip) {
|
||
alert('Ошибка! Вы не на том сервере для выдачи этого предмета.');
|
||
return;
|
||
}
|
||
|
||
await withWithdrawing(item.id, async () => {
|
||
try {
|
||
await withdrawInventoryItem({
|
||
username,
|
||
item_id: item.id,
|
||
server_ip: selectedServerIp,
|
||
});
|
||
|
||
setItems((prevItems) => prevItems.filter((prevItem) => prevItem.id !== item.id));
|
||
|
||
setSlots((prev) => {
|
||
const next = prev.map((x) => (x?.id === item.id ? null : x));
|
||
|
||
const layout: Record<string, number> = {};
|
||
next.forEach((it, idx) => {
|
||
if (it) layout[it.id] = idx;
|
||
});
|
||
|
||
setLayout(username, selectedServerIp, layout);
|
||
return next;
|
||
});
|
||
} catch (e) {
|
||
console.error('Ошибка при выводе предмета:', e);
|
||
}
|
||
});
|
||
};
|
||
|
||
const checkPlayerStatus = async () => {
|
||
if (!username) return;
|
||
|
||
setCheckingOnline(true);
|
||
try {
|
||
const res = await getPlayerServer(username);
|
||
setIsOnline(!!res?.online);
|
||
setPlayerServer(res?.server?.ip ?? null);
|
||
} catch (e) {
|
||
console.error('Ошибка проверки онлайна:', e);
|
||
setIsOnline(false);
|
||
setPlayerServer(null);
|
||
} finally {
|
||
setCheckingOnline(false);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
if (!username) return;
|
||
checkPlayerStatus();
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [username]);
|
||
|
||
const headerServerName = selectedServerIp ? translateServer(`Server ${selectedServerIp}`) : '';
|
||
|
||
if (!username) {
|
||
return (
|
||
<Box sx={{ p: '2vw' }}>
|
||
<Typography sx={{ color: 'rgba(255,255,255,0.75)' }}>
|
||
Не найдено имя игрока. Авторизуйтесь в лаунчере.
|
||
</Typography>
|
||
</Box>
|
||
);
|
||
}
|
||
|
||
const canPrev = page > 1;
|
||
const canNext = page < pages;
|
||
|
||
const handlePrev = async () => {
|
||
if (!canPrev || !username || !selectedServerIp) return;
|
||
const nextPage = page - 1;
|
||
setPage(nextPage);
|
||
setLoading(true);
|
||
try {
|
||
await loadInventory(username, selectedServerIp, nextPage);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleNext = async () => {
|
||
if (!canNext || !username || !selectedServerIp) return;
|
||
const nextPage = page + 1;
|
||
setPage(nextPage);
|
||
setLoading(true);
|
||
try {
|
||
await loadInventory(username, selectedServerIp, nextPage);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Box
|
||
sx={{
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
width: '100%',
|
||
height: '100%',
|
||
overflow: 'auto',
|
||
px: '2.5vw',
|
||
py: '2vw',
|
||
gap: 2,
|
||
mt: '12vh',
|
||
}}
|
||
>
|
||
{/* ШАПКА + ПАГИНАЦИЯ */}
|
||
<Box
|
||
sx={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: 2,
|
||
flexWrap: 'wrap',
|
||
justifyContent: 'space-evenly',
|
||
flexDirection: 'row-reverse',
|
||
}}
|
||
>
|
||
{!!selectedServerIp && (
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||
<Button
|
||
variant="outlined"
|
||
disabled={!canPrev || loading}
|
||
onClick={handlePrev}
|
||
sx={{
|
||
borderRadius: '2.5vw',
|
||
fontFamily: 'Benzin-Bold',
|
||
color: 'white',
|
||
border: '1px solid rgba(255,255,255,0.15)',
|
||
}}
|
||
>
|
||
Назад
|
||
</Button>
|
||
|
||
<Typography sx={{ color: 'rgba(255,255,255,0.7)' }}>
|
||
Страница {page} / {pages} • Всего: {total}
|
||
</Typography>
|
||
|
||
<Button
|
||
variant="outlined"
|
||
disabled={!canNext || loading}
|
||
onClick={handleNext}
|
||
sx={{
|
||
borderRadius: '2.5vw',
|
||
fontFamily: 'Benzin-Bold',
|
||
color: 'white',
|
||
border: '1px solid rgba(255,255,255,0.15)',
|
||
}}
|
||
>
|
||
Вперёд
|
||
</Button>
|
||
</Box>
|
||
)}
|
||
|
||
{availableServers.length > 0 && (
|
||
<FormControl size="small" sx={{ minWidth: 260 }}>
|
||
<InputLabel
|
||
id="inventory-server-label"
|
||
sx={{ fontFamily: 'Benzin-Bold', color: 'rgba(255,255,255,0.75)' }}
|
||
>
|
||
Сервер
|
||
</InputLabel>
|
||
|
||
<Select
|
||
labelId="inventory-server-label"
|
||
label="Сервер"
|
||
value={selectedServerIp}
|
||
onChange={(e) => setSelectedServerIp(String(e.target.value))}
|
||
MenuProps={{
|
||
PaperProps: {
|
||
sx: {
|
||
bgcolor: 'rgba(10,10,20,0.96)',
|
||
border: '1px solid rgba(255,255,255,0.10)',
|
||
borderRadius: '1vw',
|
||
backdropFilter: 'blur(14px)',
|
||
'& .MuiMenuItem-root': {
|
||
color: 'rgba(255,255,255,0.9)',
|
||
fontFamily: 'Benzin-Bold',
|
||
},
|
||
'& .MuiMenuItem-root.Mui-selected': {
|
||
backgroundColor: 'rgba(242,113,33,0.16)',
|
||
},
|
||
'& .MuiMenuItem-root:hover': {
|
||
backgroundColor: 'rgba(233,64,205,0.14)',
|
||
},
|
||
},
|
||
},
|
||
}}
|
||
sx={{
|
||
borderRadius: '999px',
|
||
bgcolor: 'rgba(255,255,255,0.04)',
|
||
color: 'rgba(255,255,255,0.92)',
|
||
fontFamily: 'Benzin-Bold',
|
||
'& .MuiSelect-select': {
|
||
py: '0.9vw',
|
||
px: '1.2vw',
|
||
},
|
||
'& .MuiOutlinedInput-notchedOutline': {
|
||
borderColor: 'rgba(255,255,255,0.14)',
|
||
},
|
||
'&:hover .MuiOutlinedInput-notchedOutline': {
|
||
borderColor: 'rgba(242,113,33,0.55)',
|
||
},
|
||
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
||
borderColor: 'rgba(233,64,205,0.65)',
|
||
borderWidth: '2px',
|
||
},
|
||
'& .MuiSelect-icon': {
|
||
color: 'rgba(255,255,255,0.75)',
|
||
},
|
||
}}
|
||
>
|
||
{availableServers.map((ip) => (
|
||
<MenuItem key={ip} value={ip}>
|
||
{translateServer(`Server ${ip}`)}
|
||
</MenuItem>
|
||
))}
|
||
</Select>
|
||
</FormControl>
|
||
)}
|
||
|
||
<Typography
|
||
variant="h6"
|
||
sx={{
|
||
fontFamily: 'Benzin-Bold',
|
||
backgroundImage:
|
||
'linear-gradient(136deg, rgb(242,113,33), rgb(233,64,87), rgb(138,35,135))',
|
||
WebkitBackgroundClip: 'text',
|
||
WebkitTextFillColor: 'transparent',
|
||
}}
|
||
>
|
||
Инвентарь {headerServerName ? `— ${headerServerName}` : ''}
|
||
</Typography>
|
||
</Box>
|
||
|
||
{/* GRID */}
|
||
{loading ? (
|
||
<FullScreenLoader fullScreen={false} message="Загрузка инвентаря..." />
|
||
) : (
|
||
<Grid container spacing={2}>
|
||
{Array.from({ length: 28 }).map((_, index) => {
|
||
const item = slots[index];
|
||
|
||
// ПУСТАЯ ЯЧЕЙКА
|
||
if (!item) {
|
||
return (
|
||
<Grid
|
||
item
|
||
xs={3}
|
||
key={index}
|
||
onMouseEnter={() => {
|
||
if (draggedItemId) setDragOverIndex(index);
|
||
}}
|
||
sx={{
|
||
outline:
|
||
draggedItemId && dragOverIndex === index
|
||
? '2px dashed rgba(255,255,255,0.4)'
|
||
: 'none',
|
||
}}
|
||
>
|
||
<Paper
|
||
elevation={0}
|
||
sx={{
|
||
...CardFacePaperSx,
|
||
overflow: 'hidden',
|
||
width: '12vw',
|
||
height: '12vw',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
color: 'rgba(255,255,255,0.5)',
|
||
}}
|
||
>
|
||
<Typography>Пусто</Typography>
|
||
</Paper>
|
||
</Grid>
|
||
);
|
||
}
|
||
|
||
const displayNameRaw =
|
||
item.item_data?.meta?.display_name ?? item.item_data?.material ?? 'Предмет';
|
||
const displayName = stripMinecraftColors(displayNameRaw);
|
||
|
||
const amount =
|
||
(item as any)?.amount ??
|
||
(item as any)?.item_data?.amount ??
|
||
(item as any)?.item_data?.meta?.amount ??
|
||
1;
|
||
|
||
const isHovered = hoveredId === item.id;
|
||
const isWithdrawing = withdrawingIds.includes(item.id);
|
||
|
||
// ✅ проверка: игрок реально онлайн на нужном сервере
|
||
const isOnRightServer = isOnline && playerServer === item.server_ip;
|
||
const canWithdraw = isOnRightServer && !loading && !checkingOnline && !isWithdrawing;
|
||
|
||
const texture = item.item_data?.material
|
||
? `https://cdn.minecraft.popa-popa.ru/textures/${item.item_data.material.toLowerCase()}.png`
|
||
: '';
|
||
|
||
return (
|
||
<Grid
|
||
item
|
||
xs={3}
|
||
key={item.id}
|
||
onMouseEnter={() => {
|
||
if (draggedItemId) setDragOverIndex(index);
|
||
}}
|
||
sx={{
|
||
outline:
|
||
draggedItemId && dragOverIndex === index
|
||
? '2px dashed rgba(255,255,255,0.4)'
|
||
: 'none',
|
||
}}
|
||
>
|
||
<Box
|
||
sx={{
|
||
width: '100%',
|
||
perspective: '1200px',
|
||
cursor: draggedItemId === item.id ? 'grabbing' : 'grab',
|
||
opacity: draggedItemId === item.id ? 0.4 : 1,
|
||
}}
|
||
onMouseEnter={() => setHoveredId(item.id)}
|
||
onMouseLeave={() => setHoveredId(null)}
|
||
onMouseDown={(e) => {
|
||
const target = e.target as HTMLElement;
|
||
if (target.closest('button')) return;
|
||
|
||
e.preventDefault();
|
||
setDraggedItemId(item.id);
|
||
setDraggedFromIndex(index);
|
||
setDragPos({ x: e.clientX, y: e.clientY });
|
||
}}
|
||
>
|
||
<Box
|
||
sx={{
|
||
position: 'relative',
|
||
width: '12vw',
|
||
height: '12vw', // фиксированная высота = Grid не дергается
|
||
transformStyle: 'preserve-3d',
|
||
transition: 'transform 0.5s cubic-bezier(0.4, 0.2, 0.2, 1)',
|
||
transform: isHovered ? 'rotateY(180deg)' : 'rotateY(0deg)',
|
||
}}
|
||
>
|
||
{/* FRONT */}
|
||
<Paper
|
||
elevation={0}
|
||
sx={{
|
||
...CardFacePaperSx,
|
||
position: 'absolute',
|
||
inset: 0,
|
||
backfaceVisibility: 'hidden',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
overflow: 'hidden',
|
||
}}
|
||
>
|
||
<Box
|
||
component="img"
|
||
src={texture}
|
||
sx={{
|
||
width: '5vw',
|
||
height: '5vw',
|
||
objectFit: 'contain',
|
||
imageRendering: 'pixelated',
|
||
userSelect: 'none',
|
||
transition: 'transform 0.25s ease',
|
||
transform: isHovered ? 'scale(1.05)' : 'scale(1)',
|
||
}}
|
||
draggable={false}
|
||
alt={displayName}
|
||
/>
|
||
</Paper>
|
||
|
||
{/* BACK */}
|
||
<Paper
|
||
elevation={0}
|
||
sx={{
|
||
...CardFacePaperSx,
|
||
position: 'absolute',
|
||
inset: 0,
|
||
backfaceVisibility: 'hidden',
|
||
transform: 'rotateY(180deg)',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
justifyContent: 'center',
|
||
gap: '0.8vw',
|
||
px: '1.1vw',
|
||
overflow: 'hidden',
|
||
}}
|
||
>
|
||
<Typography
|
||
sx={{
|
||
fontFamily: 'Benzin-Bold',
|
||
fontSize: '0.8vw',
|
||
lineHeight: 1.2,
|
||
textAlign: 'center',
|
||
wordBreak: 'break-word',
|
||
}}
|
||
>
|
||
{displayName}
|
||
</Typography>
|
||
|
||
<Typography
|
||
sx={{
|
||
fontSize: '0.7vw',
|
||
textAlign: 'center',
|
||
color: 'rgba(255,255,255,0.7)',
|
||
}}
|
||
>
|
||
Кол-во: {amount}
|
||
</Typography>
|
||
|
||
{!isOnRightServer ? (
|
||
<CustomTooltip
|
||
essential
|
||
title={
|
||
!isOnline
|
||
? 'Вы должны быть онлайн на сервере'
|
||
: `Перейдите на сервер ${item.server_ip}`
|
||
}
|
||
placement="top"
|
||
arrow
|
||
>
|
||
<span>
|
||
<Button
|
||
disabled
|
||
fullWidth
|
||
variant="outlined"
|
||
sx={{
|
||
fontSize: '0.8vw',
|
||
borderRadius: '2vw',
|
||
fontFamily: 'Benzin-Bold',
|
||
border: '1px solid rgba(255,255,255,0.15)',
|
||
color: 'rgba(255,255,255,0.5)',
|
||
}}
|
||
>
|
||
Выдать
|
||
</Button>
|
||
</span>
|
||
</CustomTooltip>
|
||
) : (
|
||
<Button
|
||
fullWidth
|
||
variant="outlined"
|
||
disabled={!canWithdraw}
|
||
onClick={(e) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
handleWithdraw(item);
|
||
}}
|
||
sx={{
|
||
fontSize: '0.8vw',
|
||
borderRadius: '2vw',
|
||
fontFamily: 'Benzin-Bold',
|
||
border: '1px solid rgba(255,255,255,0.15)',
|
||
color: 'white',
|
||
}}
|
||
>
|
||
{isWithdrawing ? 'Выдача...' : 'Выдать'}
|
||
</Button>
|
||
)}
|
||
</Paper>
|
||
</Box>
|
||
</Box>
|
||
</Grid>
|
||
);
|
||
})}
|
||
</Grid>
|
||
)}
|
||
{draggedItemId && dragPos && (() => {
|
||
const draggedItem = items.find(i => i.id === draggedItemId);
|
||
if (!draggedItem) return null;
|
||
|
||
const texture = `https://cdn.minecraft.popa-popa.ru/textures/${draggedItem.item_data.material.toLowerCase()}.png`;
|
||
|
||
return (
|
||
<Box
|
||
sx={{
|
||
position: 'fixed',
|
||
left: dragPos.x,
|
||
top: dragPos.y,
|
||
transform: 'translate(-50%, -50%)',
|
||
//width: '12vw',
|
||
//height: '12vw',
|
||
pointerEvents: 'none',
|
||
zIndex: 9999,
|
||
opacity: 0.9,
|
||
}}
|
||
>
|
||
<Paper
|
||
elevation={0}
|
||
sx={{
|
||
...CardFacePaperSx,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
width: '12vw',
|
||
height: '12vw',
|
||
}}
|
||
>
|
||
<Box
|
||
component="img"
|
||
src={texture}
|
||
sx={{
|
||
width: '5vw',
|
||
height: '5vw',
|
||
imageRendering: 'pixelated',
|
||
}}
|
||
/>
|
||
</Paper>
|
||
</Box>
|
||
);
|
||
})()}
|
||
</Box>
|
||
);
|
||
}
|