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 styles from './bag.module.css';
import Slot from './slot.jsx';
import Grid from './grid.jsx';
/** /**
* Inventory bag. 10-40 slots of inventory. * Inventory bag. 10-40 slots of inventory.
*/ */
export default function Bag({ export default function Bag({
isInventoryOpen, isInventoryOpen,
onActivate,
slots, 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 ( return (
<div <div
className={styles.bag} 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> </div>
); );
} }

View File

@ -1,31 +1,10 @@
.bag { .bag {
align-self: left; 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; display: inline-block;
left: calc(var(--unit) * 20px); left: 20px;
line-height: 0; line-height: 0;
opacity: 1;
position: absolute; position: absolute;
top: calc(var(--unit) * 90px); top: 74px;
transition: left 150ms; transition: left 150ms, opacity 200ms;
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);
}
} }

View File

@ -32,7 +32,6 @@ export default function Dom({children}) {
<style>{` <style>{`
.${styles.dom}{ .${styles.dom}{
--scale: ${scale}; --scale: ${scale};
--unit: calc(${RESOLUTION.x} / 1000);
} }
`}</style> `}</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 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. * The hotbar. 10 slots of inventory with an active selection.
@ -10,48 +12,24 @@ export default function Hotbar({
onActivate, onActivate,
slots, 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 ( return (
<div <div
className={styles.hotbar} 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> <style>{`
{Slots} .${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> </div>
); );
} }

View File

@ -1,15 +1,12 @@
.hotbar { .hotbar {
align-self: left; 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; display: inline-block;
left: calc(var(--unit) * 20px); left: 20px;
line-height: 0; line-height: 0;
opacity: 1;
position: absolute; position: absolute;
top: calc(var(--unit) * 20px); top: 4px;
transition: top 150ms; transition: top 150ms, opacity 200ms;
} }
.label { .label {
@ -28,27 +25,3 @@
top: -17.5px; top: -17.5px;
transform: translateX(-50%); 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 { .slot {
--size: calc(var(--unit) * 50px); --size: 40px;
--space: calc(var(--unit) * 10px); --space: 7px;
background-color: transparent; background-color: transparent;
background-position: center; background-position: center;
background-repeat: no-repeat; 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 {useClient, usePacket} from '@/react/context/client.js';
import {useDebug} from '@/react/context/debug.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 Bag from './dom/bag.jsx';
import Dom from './dom/dom.jsx'; import Dom from './dom/dom.jsx';
import Entities from './dom/entities.jsx'; import Entities from './dom/entities.jsx';
import Grid from './dom/grid.jsx';
import HotBar from './dom/hotbar.jsx'; import HotBar from './dom/hotbar.jsx';
import Pixi from './pixi/pixi.jsx'; import Pixi from './pixi/pixi.jsx';
import Devtools from './devtools.jsx'; import Devtools from './devtools.jsx';
@ -25,7 +26,7 @@ const KEY_MAP = {
}; };
function emptySlots() { function emptySlots() {
return Array(10).fill(undefined); return Array(40).fill(undefined);
} }
const devEventsChannel = new EventEmitter(); const devEventsChannel = new EventEmitter();
@ -43,7 +44,7 @@ function Ui({disconnected}) {
const [devtoolsIsOpen, setDevtoolsIsOpen] = useState(false); const [devtoolsIsOpen, setDevtoolsIsOpen] = useState(false);
const ratio = (RESOLUTION.x * (devtoolsIsOpen ? 2 : 1)) / RESOLUTION.y; const ratio = (RESOLUTION.x * (devtoolsIsOpen ? 2 : 1)) / RESOLUTION.y;
const [camera, setCamera] = useState({x: 0, y: 0}); const [camera, setCamera] = useState({x: 0, y: 0});
const [hotbarSlots, setHotbarSlots] = useState(emptySlots()); const [inventorySlots, setInventorySlots] = useState(emptySlots());
const [activeSlot, setActiveSlot] = useState(0); const [activeSlot, setActiveSlot] = useState(0);
const [scale, setScale] = useState(2); const [scale, setScale] = useState(2);
const [Components, setComponents] = useState(); const [Components, setComponents] = useState();
@ -123,6 +124,17 @@ function Ui({disconnected}) {
chatIsOpen, chatIsOpen,
client, client,
]); ]);
const keepHotbarOpen = useCallback(() => {
if (!isInventoryOpen) {
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
}, [hotbarHideHandle, isInventoryOpen]);
useEffect(() => { useEffect(() => {
return addKeyListener(document.body, ({event, type, payload}) => { return addKeyListener(document.body, ({event, type, payload}) => {
if ('Escape' === payload && 'keyDown' === type && chatIsOpen) { if ('Escape' === payload && 'keyDown' === type && chatIsOpen) {
@ -166,7 +178,10 @@ function Ui({disconnected}) {
} }
break; break;
} }
case 'Tab': { case '`': {
if (event) {
event.preventDefault();
}
if ('keyDown' === type) { if ('keyDown' === type) {
if (isInventoryOpen) { if (isInventoryOpen) {
setHotbarIsHidden(true); setHotbarIsHidden(true);
@ -199,150 +214,70 @@ function Ui({disconnected}) {
} }
case '1': { case '1': {
if ('keyDown' === type) { if ('keyDown' === type) {
if (!isInventoryOpen) { keepHotbarOpen();
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
actionPayload = {type: 'changeSlot', value: 1}; actionPayload = {type: 'changeSlot', value: 1};
} }
break; break;
} }
case '2': { case '2': {
if ('keyDown' === type) { if ('keyDown' === type) {
if (!isInventoryOpen) { keepHotbarOpen();
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
actionPayload = {type: 'changeSlot', value: 2}; actionPayload = {type: 'changeSlot', value: 2};
} }
break; break;
} }
case '3': { case '3': {
if ('keyDown' === type) { if ('keyDown' === type) {
if (!isInventoryOpen) { keepHotbarOpen();
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
actionPayload = {type: 'changeSlot', value: 3}; actionPayload = {type: 'changeSlot', value: 3};
} }
break; break;
} }
case '4': { case '4': {
if ('keyDown' === type) { if ('keyDown' === type) {
if (!isInventoryOpen) { keepHotbarOpen();
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
actionPayload = {type: 'changeSlot', value: 4}; actionPayload = {type: 'changeSlot', value: 4};
} }
break; break;
} }
case '5': { case '5': {
if ('keyDown' === type) { if ('keyDown' === type) {
if (!isInventoryOpen) { keepHotbarOpen();
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
actionPayload = {type: 'changeSlot', value: 5}; actionPayload = {type: 'changeSlot', value: 5};
} }
break; break;
} }
case '6': { case '6': {
if ('keyDown' === type) { if ('keyDown' === type) {
if (!isInventoryOpen) { keepHotbarOpen();
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
actionPayload = {type: 'changeSlot', value: 6}; actionPayload = {type: 'changeSlot', value: 6};
} }
break; break;
} }
case '7': { case '7': {
if ('keyDown' === type) { if ('keyDown' === type) {
if (!isInventoryOpen) { keepHotbarOpen();
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
actionPayload = {type: 'changeSlot', value: 7}; actionPayload = {type: 'changeSlot', value: 7};
} }
break; break;
} }
case '8': { case '8': {
if ('keyDown' === type) { if ('keyDown' === type) {
if (!isInventoryOpen) { keepHotbarOpen();
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
actionPayload = {type: 'changeSlot', value: 8}; actionPayload = {type: 'changeSlot', value: 8};
} }
break; break;
} }
case '9': { case '9': {
if ('keyDown' === type) { if ('keyDown' === type) {
if (!isInventoryOpen) { keepHotbarOpen();
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
actionPayload = {type: 'changeSlot', value: 9}; actionPayload = {type: 'changeSlot', value: 9};
} }
break; break;
} }
case '0': { case '0': {
if ('keyDown' === type) { if ('keyDown' === type) {
if (!isInventoryOpen) { keepHotbarOpen();
setHotbarIsHidden(false);
if (hotbarHideHandle) {
clearTimeout(hotbarHideHandle);
}
setHotbarHideHandle(setTimeout(() => {
setHotbarIsHidden(true);
}, 4000));
}
actionPayload = {type: 'changeSlot', value: 10}; actionPayload = {type: 'changeSlot', value: 10};
} }
break; break;
@ -362,6 +297,7 @@ function Ui({disconnected}) {
devtoolsIsOpen, devtoolsIsOpen,
hotbarHideHandle, hotbarHideHandle,
isInventoryOpen, isInventoryOpen,
keepHotbarOpen,
monopolizers, monopolizers,
setDebug, setDebug,
setScale, setScale,
@ -400,11 +336,11 @@ function Ui({disconnected}) {
if (localMainEntity === id) { if (localMainEntity === id) {
if (update.Inventory) { if (update.Inventory) {
setBufferSlot(entity.Inventory.item(0)); setBufferSlot(entity.Inventory.item(0));
const newHotbarSlots = emptySlots(); const newInventorySlots = emptySlots();
for (let i = 1; i < 11; ++i) { for (let i = 1; i < 41; ++i) {
newHotbarSlots[i - 1] = entity.Inventory.item(i); newInventorySlots[i - 1] = entity.Inventory.item(i);
} }
setHotbarSlots(newHotbarSlots); setInventorySlots(newInventorySlots);
} }
if (update.Wielder && 'activeSlot' in update.Wielder) { if (update.Wielder && 'activeSlot' in update.Wielder) {
setActiveSlot(update.Wielder.activeSlot); setActiveSlot(update.Wielder.activeSlot);
@ -568,16 +504,24 @@ function Ui({disconnected}) {
active={activeSlot} active={activeSlot}
hotbarIsHidden={hotbarIsHidden} hotbarIsHidden={hotbarIsHidden}
onActivate={(i) => { onActivate={(i) => {
keepHotbarOpen();
client.send({ client.send({
type: 'Action', type: 'Action',
payload: {type: 'swapSlots', value: [0, i + 1]}, payload: {type: 'swapSlots', value: [0, i + 1]},
}); });
}} }}
slots={hotbarSlots} slots={inventorySlots.slice(0, 10)}
/> />
<Bag <Bag
isInventoryOpen={isInventoryOpen} 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 <Entities
camera={camera} 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