refactor: inventory UI

This commit is contained in:
cha0s 2024-07-28 08:06:08 -05:00
parent b37d3513f6
commit ce9a1aeba7
12 changed files with 198 additions and 237 deletions

View File

@ -1,54 +1,28 @@
import styles from './bag.module.css';
import Slot from './slot.jsx';
import Grid from './grid.jsx';
/**
* Inventory bag. 10-40 slots of inventory.
*/
export default function Bag({
isInventoryOpen,
onActivate,
slots,
}) {
const Slots = slots.map((slot, i) => (
<div
className={
[styles.slotWrapper]
.filter(Boolean).join(' ')
}
key={i}
>
<Slot
icon={slot?.icon}
// onMouseDown={(event) => {
// onActivate(i)
// event.stopPropagation();
// }}
// onMouseUp={(event) => {
// event.stopPropagation();
// }}
// onDragOver={(event) => {
// event.preventDefault();
// }}
// onDragStart={(event) => {
// if (!slot) {
// event.preventDefault();
// }
// event.dataTransfer.setData('silphius/item', i);
// onActivate(i);
// }}
// onDrop={(event) => {
// event.preventDefault();
// onActivate(i);
// }}
qty={slot?.qty}
/>
</div>
));
return (
<div
className={styles.bag}
style={isInventoryOpen ? {transition: 'none'} : {left: '-440px'}}
style={isInventoryOpen ? {transition: 'opacity 50ms'} : {opacity: 0, left: '-440px'}}
>
{Slots}
<Grid
color="rgba(02, 02, 28, 0.6)"
columns={10}
label="Bag"
onActivate={onActivate}
slots={slots}
/>
</div>
);
}

View File

@ -1,31 +1,10 @@
.bag {
align-self: left;
--border: calc(var(--unit) * 3px);
background-color: rgba(02, 02, 28, 0.6);
border: var(--border) solid #444444;
box-sizing: border-box;
display: inline-block;
left: calc(var(--unit) * 20px);
left: 20px;
line-height: 0;
opacity: 1;
position: absolute;
top: calc(var(--unit) * 90px);
transition: left 150ms;
max-width: 430.5px;
}
.slotWrapper {
border: var(--border) solid #999999;
box-sizing: border-box;
display: inline-block;
line-height: 0;
padding: 0;
&:not(:nth-child(10n)) {
border-right: none;
}
&:not(:nth-last-of-type(-n+10)) {
border-bottom: none;
}
&:hover {
background-color: rgba(0, 0, 0, 0.2);
}
top: 74px;
transition: left 150ms, opacity 200ms;
}

View File

@ -32,7 +32,6 @@ export default function Dom({children}) {
<style>{`
.${styles.dom}{
--scale: ${scale};
--unit: calc(${RESOLUTION.x} / 1000);
}
`}</style>
)}

View File

@ -0,0 +1,69 @@
import styles from './grid.module.css';
import Slot from './slot.jsx';
/**
* Inventory grid.
*/
export default function Grid({
active = -1,
color,
columns,
label,
onActivate,
slots,
}) {
const Slots = slots.map((slot, i) => (
<div
className={
[styles.slot, active === i && styles.active]
.filter(Boolean).join(' ')
}
key={i}
>
<Slot
icon={slot?.icon}
onMouseDown={(event) => {
onActivate(i)
event.stopPropagation();
}}
onMouseUp={(event) => {
event.stopPropagation();
}}
onDragOver={(event) => {
event.preventDefault();
}}
onDragStart={(event) => {
if (!slot) {
event.preventDefault();
}
event.dataTransfer.setData('silphius/item', i);
onActivate(i);
}}
onDrop={(event) => {
event.preventDefault();
onActivate(i);
}}
qty={slot?.qty}
/>
</div>
));
return (
<div className={styles.gridWrapper}>
<p className={styles.label}>{label}</p>
<div
className={styles.grid}
style={{
'--color': color,
'--columns': columns,
}}
>
<div
className={styles.innerGrid}
>
{Slots}
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,45 @@
.grid {
--border: 2.5px;
border: var(--border) solid #444444;
line-height: 0;
opacity: 1;
position: relative;
}
.innerGrid {
background-color: var(--color);
border-bottom: var(--border) solid #999999;
border-right: var(--border) solid #999999;
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
}
.label {
background-color: transparent;
color: white;
font-family: Cookbook, Georgia, 'Times New Roman', Times, serif;
margin: 0;
text-shadow:
0px -1px 0px black,
1px 0px 0px black,
0px 1px 0px black,
-1px 0px 0px black
;
}
.slot {
border-left: var(--border) solid #999999;
border-top: var(--border) solid #999999;
display: inline-block;
line-height: 0;
padding: 0;
&:hover {
background-color: rgba(0, 0, 0, 0.2);
}
&.active {
border: var(--border) solid yellow;
margin-bottom: calc(-1 * var(--border));
margin-right: calc(-1 * var(--border));
z-index: 1;
}
}

View File

@ -1,5 +1,7 @@
import styles from './hotbar.module.css';
import Slot from './slot.jsx';
import gridStyles from './grid.module.css';
import Grid from './grid.jsx';
/**
* The hotbar. 10 slots of inventory with an active selection.
@ -10,48 +12,24 @@ export default function Hotbar({
onActivate,
slots,
}) {
const Slots = slots.map((slot, i) => (
<div
className={
[styles.slotWrapper, active === i && styles.active]
.filter(Boolean).join(' ')
}
key={i}
>
<Slot
icon={slot?.icon}
onMouseDown={(event) => {
onActivate(i)
event.stopPropagation();
}}
onMouseUp={(event) => {
event.stopPropagation();
}}
onDragOver={(event) => {
event.preventDefault();
}}
onDragStart={(event) => {
if (!slot) {
event.preventDefault();
}
event.dataTransfer.setData('silphius/item', i);
onActivate(i);
}}
onDrop={(event) => {
event.preventDefault();
onActivate(i);
}}
qty={slot?.qty}
/>
</div>
));
return (
<div
className={styles.hotbar}
style={hotbarIsHidden ? {top: '-50px'} : {transition: 'none'}}
style={hotbarIsHidden ? {opacity: 0, top: '-50px'} : {transition: 'opacity 50ms'}}
>
<p className={styles.label}>{slots[active] && slots[active].label}</p>
{Slots}
<style>{`
.${styles.hotbar} .${gridStyles.label} {
text-align: center;
}
`}</style>
<Grid
active={active}
color="rgba(02, 02, 57, 0.6)"
columns={10}
label={slots[active] && slots[active].label}
onActivate={onActivate}
slots={slots}
/>
</div>
);
}

View File

@ -1,15 +1,12 @@
.hotbar {
align-self: left;
--border: calc(var(--unit) * 3px);
background-color: rgba(02, 02, 57, 0.6);
border: var(--border) solid #444444;
box-sizing: border-box;
display: inline-block;
left: calc(var(--unit) * 20px);
left: 20px;
line-height: 0;
opacity: 1;
position: absolute;
top: calc(var(--unit) * 20px);
transition: top 150ms;
top: 4px;
transition: top 150ms, opacity 200ms;
}
.label {
@ -28,27 +25,3 @@
top: -17.5px;
transform: translateX(-50%);
}
.slotWrapper {
border: var(--border) solid #999999;
box-sizing: border-box;
display: inline-block;
line-height: 0;
padding: 0;
&.active + .slotWrapper {
border-left: none;
}
&:not(:last-child) {
border-right: none;
}
&.active {
border-right: var(--border) solid #999999;
border-color: yellow;
}
&:hover {
background-color: rgba(0, 0, 0, 0.2);
}
}

View File

@ -1,6 +1,6 @@
.slot {
--size: calc(var(--unit) * 50px);
--space: calc(var(--unit) * 10px);
--size: 40px;
--space: 7px;
background-color: transparent;
background-position: center;
background-repeat: no-repeat;

View File

@ -1,4 +1,4 @@
import {memo, useCallback, useEffect, useRef, useState} from 'react';
import {useCallback, useEffect, useRef, useState} from 'react';
import {useClient, usePacket} from '@/react/context/client.js';
import {useDebug} from '@/react/context/debug.js';
@ -14,6 +14,7 @@ import Chat from './dom/chat/chat.jsx';
import Bag from './dom/bag.jsx';
import Dom from './dom/dom.jsx';
import Entities from './dom/entities.jsx';
import Grid from './dom/grid.jsx';
import HotBar from './dom/hotbar.jsx';
import Pixi from './pixi/pixi.jsx';
import Devtools from './devtools.jsx';
@ -25,7 +26,7 @@ const KEY_MAP = {
};
function emptySlots() {
return Array(10).fill(undefined);
return Array(40).fill(undefined);
}
const devEventsChannel = new EventEmitter();
@ -43,7 +44,7 @@ function Ui({disconnected}) {
const [devtoolsIsOpen, setDevtoolsIsOpen] = useState(false);
const ratio = (RESOLUTION.x * (devtoolsIsOpen ? 2 : 1)) / RESOLUTION.y;
const [camera, setCamera] = useState({x: 0, y: 0});
const [hotbarSlots, setHotbarSlots] = useState(emptySlots());
const [inventorySlots, setInventorySlots] = useState(emptySlots());
const [activeSlot, setActiveSlot] = useState(0);
const [scale, setScale] = useState(2);
const [Components, setComponents] = useState();
@ -123,6 +124,17 @@ function Ui({disconnected}) {
chatIsOpen,
client,
]);
const keepHotbarOpen = useCallback(() => {
if (!isInventoryOpen) {
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
}, [hotbarHideHandle, isInventoryOpen]);
useEffect(() => {
return addKeyListener(document.body, ({event, type, payload}) => {
if ('Escape' === payload && 'keyDown' === type && chatIsOpen) {
@ -166,7 +178,10 @@ function Ui({disconnected}) {
}
break;
}
case 'Tab': {
case '`': {
if (event) {
event.preventDefault();
}
if ('keyDown' === type) {
if (isInventoryOpen) {
setHotbarIsHidden(true);
@ -199,150 +214,70 @@ function Ui({disconnected}) {
}
case '1': {
if ('keyDown' === type) {
if (!isInventoryOpen) {
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
keepHotbarOpen();
actionPayload = {type: 'changeSlot', value: 1};
}
break;
}
case '2': {
if ('keyDown' === type) {
if (!isInventoryOpen) {
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
keepHotbarOpen();
actionPayload = {type: 'changeSlot', value: 2};
}
break;
}
case '3': {
if ('keyDown' === type) {
if (!isInventoryOpen) {
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
keepHotbarOpen();
actionPayload = {type: 'changeSlot', value: 3};
}
break;
}
case '4': {
if ('keyDown' === type) {
if (!isInventoryOpen) {
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
keepHotbarOpen();
actionPayload = {type: 'changeSlot', value: 4};
}
break;
}
case '5': {
if ('keyDown' === type) {
if (!isInventoryOpen) {
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
keepHotbarOpen();
actionPayload = {type: 'changeSlot', value: 5};
}
break;
}
case '6': {
if ('keyDown' === type) {
if (!isInventoryOpen) {
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
keepHotbarOpen();
actionPayload = {type: 'changeSlot', value: 6};
}
break;
}
case '7': {
if ('keyDown' === type) {
if (!isInventoryOpen) {
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
keepHotbarOpen();
actionPayload = {type: 'changeSlot', value: 7};
}
break;
}
case '8': {
if ('keyDown' === type) {
if (!isInventoryOpen) {
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
keepHotbarOpen();
actionPayload = {type: 'changeSlot', value: 8};
}
break;
}
case '9': {
if ('keyDown' === type) {
if (!isInventoryOpen) {
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
keepHotbarOpen();
actionPayload = {type: 'changeSlot', value: 9};
}
break;
}
case '0': {
if ('keyDown' === type) {
if (!isInventoryOpen) {
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
keepHotbarOpen();
actionPayload = {type: 'changeSlot', value: 10};
}
break;
@ -362,6 +297,7 @@ function Ui({disconnected}) {
devtoolsIsOpen,
hotbarHideHandle,
isInventoryOpen,
keepHotbarOpen,
monopolizers,
setDebug,
setScale,
@ -400,11 +336,11 @@ function Ui({disconnected}) {
if (localMainEntity === id) {
if (update.Inventory) {
setBufferSlot(entity.Inventory.item(0));
const newHotbarSlots = emptySlots();
for (let i = 1; i < 11; ++i) {
newHotbarSlots[i - 1] = entity.Inventory.item(i);
const newInventorySlots = emptySlots();
for (let i = 1; i < 41; ++i) {
newInventorySlots[i - 1] = entity.Inventory.item(i);
}
setHotbarSlots(newHotbarSlots);
setInventorySlots(newInventorySlots);
}
if (update.Wielder && 'activeSlot' in update.Wielder) {
setActiveSlot(update.Wielder.activeSlot);
@ -568,16 +504,24 @@ function Ui({disconnected}) {
active={activeSlot}
hotbarIsHidden={hotbarIsHidden}
onActivate={(i) => {
keepHotbarOpen();
client.send({
type: 'Action',
payload: {type: 'swapSlots', value: [0, i + 1]},
});
}}
slots={hotbarSlots}
slots={inventorySlots.slice(0, 10)}
/>
<Bag
isInventoryOpen={isInventoryOpen}
slots={Array(30).fill(undefined)}
onActivate={(i) => {
keepHotbarOpen();
client.send({
type: 'Action',
payload: {type: 'swapSlots', value: [0, i + 11]},
});
}}
slots={inventorySlots.slice(10)}
/>
<Entities
camera={camera}
@ -618,4 +562,4 @@ function Ui({disconnected}) {
);
}
export default memo(Ui);
export default Ui;

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB