diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 07ca67ae7..91a178d05 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -247,7 +247,7 @@ importers:
version: 1.0.0
minecraft-inventory-gui:
specifier: github:zardoy/minecraft-inventory-gui#next
- version: github.com/zardoy/minecraft-inventory-gui/bf2e7ba3afdc606604d85682d081778281d533cb(@types/react@18.2.20)(react@18.2.0)
+ version: github.com/zardoy/minecraft-inventory-gui/6685fa4a10590a5decef90a454ba0b9e045e7737(@types/react@18.2.20)(react@18.2.0)
mineflayer:
specifier: github:PrismarineJS/mineflayer
version: github.com/PrismarineJS/mineflayer/d77a0511d02fe552170249ec19c2ff3030d17137
@@ -15419,9 +15419,9 @@ packages:
vec3: 0.1.8
dev: false
- github.com/zardoy/minecraft-inventory-gui/bf2e7ba3afdc606604d85682d081778281d533cb(@types/react@18.2.20)(react@18.2.0):
- resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/bf2e7ba3afdc606604d85682d081778281d533cb}
- id: github.com/zardoy/minecraft-inventory-gui/bf2e7ba3afdc606604d85682d081778281d533cb
+ github.com/zardoy/minecraft-inventory-gui/6685fa4a10590a5decef90a454ba0b9e045e7737(@types/react@18.2.20)(react@18.2.0):
+ resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/6685fa4a10590a5decef90a454ba0b9e045e7737}
+ id: github.com/zardoy/minecraft-inventory-gui/6685fa4a10590a5decef90a454ba0b9e045e7737
name: minecraft-inventory-gui
version: 1.0.1
dependencies:
diff --git a/src/react/ChatContainer.css b/src/react/ChatContainer.css
index 69da94b02..419177835 100644
--- a/src/react/ChatContainer.css
+++ b/src/react/ChatContainer.css
@@ -47,7 +47,8 @@ div.chat-wrapper {
.input-mobile .chat-completions {
transform: none;
top: 100%;
- padding-left: 0;
+ padding-left: calc(env(safe-area-inset-left) / 2);
+ margin-top: 20px;
/* input height */
}
diff --git a/src/react/ChatContainer.tsx b/src/react/ChatContainer.tsx
index a468313ec..e965a3265 100644
--- a/src/react/ChatContainer.tsx
+++ b/src/react/ChatContainer.tsx
@@ -6,6 +6,7 @@ import { miscUiState } from '../globalState'
import { MessagePart } from './MessageFormatted'
import './ChatContainer.css'
import { isIos } from './utils'
+import { reactKeyForMessage } from './Scoreboard'
export type Message = {
parts: MessageFormatPart[],
@@ -145,7 +146,7 @@ export default ({ messages, opacity = 1, fetchCompletionItems, opened, sendMessa
const auxInputFocus = (fireKey: string) => {
chatInput.current.focus()
- document.dispatchEvent(new KeyboardEvent('keydown', { code: fireKey }))
+ chatInput.current.dispatchEvent(new KeyboardEvent('keydown', { code: fireKey, bubbles: true }))
}
const getDefaultCompleteValue = () => {
@@ -206,7 +207,7 @@ export default ({ messages, opacity = 1, fetchCompletionItems, opened, sendMessa
}}>
{opacity &&
{messages.map((m) => (
-
+
))}
|| undefined}
diff --git a/src/react/MessageFormattedString.tsx b/src/react/MessageFormattedString.tsx
index 11b29e6be..d1f8a10f0 100644
--- a/src/react/MessageFormattedString.tsx
+++ b/src/react/MessageFormattedString.tsx
@@ -4,10 +4,16 @@ import { formatMessage } from '../botUtils'
import MessageFormatted from './MessageFormatted'
/** like MessageFormatted, but receives raw string or json instead, uses window.loadedData */
-export default ({ message }: { message: string | Record }) => {
+export default ({ message }: { message: string | Record | null }) => {
const messageJson = useMemo(() => {
- return formatMessage(typeof message === 'string' ? fromFormattedString(message) : message)
+ if (!message) return null
+ try {
+ return formatMessage(typeof message === 'string' ? fromFormattedString(message) : message)
+ } catch (err) {
+ console.error(err) // todo ensure its being logged
+ return null
+ }
}, [message])
- return
+ return messageJson ? : null
}
diff --git a/src/react/Scoreboard.css b/src/react/Scoreboard.css
new file mode 100644
index 000000000..b2bb85213
--- /dev/null
+++ b/src/react/Scoreboard.css
@@ -0,0 +1,37 @@
+.scoreboard-container {
+ z-index: -2;
+ pointer-events: none;
+ white-space: nowrap;
+ position: fixed;
+ right: 0px;
+ top: 50%;
+ transform: translateY(-50%);
+ background-color: rgba(0, 0, 0, 0.4);
+ min-width: 100px;
+ max-width: 400px;
+ display: flex;
+ flex-direction: column;
+ padding-inline: 5px;
+ font-size: 0.5rem;
+ max-height: 70%;
+ overflow: hidden;
+}
+
+@media (max-width: 800px) {
+ .scoreboard-container {
+ font-size: 0.4rem;
+ }
+}
+
+.scoreboard-title {
+ text-align: center;
+}
+
+.item-container {
+ display: flex;
+}
+
+.item-value {
+ margin-left: auto;
+ color: lightcoral;
+}
diff --git a/src/react/Scoreboard.stories.tsx b/src/react/Scoreboard.stories.tsx
new file mode 100644
index 000000000..76d37c171
--- /dev/null
+++ b/src/react/Scoreboard.stories.tsx
@@ -0,0 +1,27 @@
+import type { Meta, StoryObj } from '@storybook/react'
+
+import Scoreboard from './Scoreboard'
+
+const meta: Meta = {
+ component: Scoreboard
+}
+
+export default meta
+type Story = StoryObj;
+
+export const Primary: Story = {
+ args: {
+ title: 'Scoreboard',
+ items: [
+ {
+ name: 'item 1',
+ value: 9
+ },
+ {
+ name: 'item 2',
+ value: 8
+ }
+ ],
+ open: true
+ }
+}
diff --git a/src/react/Scoreboard.tsx b/src/react/Scoreboard.tsx
new file mode 100644
index 000000000..546a0f586
--- /dev/null
+++ b/src/react/Scoreboard.tsx
@@ -0,0 +1,40 @@
+import './Scoreboard.css'
+import MessageFormattedString from './MessageFormattedString'
+
+
+export type ScoreboardItems = Array<{name: string, value: number, displayName?: any}>
+
+type ScoreboardProps = {
+ title: string,
+ items: ScoreboardItems,
+ open: boolean
+}
+
+export const reactKeyForMessage = (message) => {
+ return typeof message === 'string' ? message : JSON.stringify(message)
+}
+
+export default function Scoreboard ({ title, items, open }: ScoreboardProps) {
+
+ if (!open) return null
+ return (
+
+
+
+
+ {
+ items.map((item) => {
+ const message = item.displayName ?? item.name
+ return
+
+
+
+
+ {item.value}
+
+
+ })
+ }
+
+ )
+}
diff --git a/src/react/ScoreboardProvider.tsx b/src/react/ScoreboardProvider.tsx
new file mode 100644
index 000000000..c354513b4
--- /dev/null
+++ b/src/react/ScoreboardProvider.tsx
@@ -0,0 +1,41 @@
+import { useMemo, useState } from 'react'
+import Scoreboard from './Scoreboard'
+import type { ScoreboardItems } from './Scoreboard'
+
+
+export default function ScoreboardProvider () {
+ const [title, setTitle] = useState('Scoreboard')
+ const [items, setItems] = useState([])
+ const [open, setOpen] = useState(false)
+
+ useMemo(() => { // useMemo instead of useEffect to register them asap and not after the initial dom render
+ const updateSidebarScoreboard = () => {
+ if (bot.scoreboard.sidebar) {
+ setTitle(bot.scoreboard.sidebar.title)
+ setItems([...bot.scoreboard.sidebar.items])
+ setOpen(true)
+ } else {
+ setOpen(false)
+ }
+ }
+
+ bot.on('scoreboardCreated', updateSidebarScoreboard) // not used atm but still good to have
+ bot.on('scoreboardTitleChanged', updateSidebarScoreboard)
+ bot.on('scoreUpdated', updateSidebarScoreboard)
+ bot.on('scoreRemoved', updateSidebarScoreboard)
+ bot.on('scoreboardDeleted', updateSidebarScoreboard)
+ bot.on('scoreboardPosition', () => {
+ void Promise.resolve().then(() => {
+ updateSidebarScoreboard()
+ }) // mineflayer bug: wait for the next tick to get the correct scoreboard position
+ })
+ }, [])
+
+ return(
+
+ )
+}
diff --git a/src/reactUi.tsx b/src/reactUi.tsx
index af637853d..45415578a 100644
--- a/src/reactUi.tsx
+++ b/src/reactUi.tsx
@@ -15,6 +15,7 @@ import SelectOption from './react/SelectOption'
import EnterFullscreenButton from './react/EnterFullscreenButton'
import ChatProvider from './react/ChatProvider'
import TitleProvider from './react/TitleProvider'
+import ScoreboardProvider from './react/ScoreboardProvider'
import SoundMuffler from './react/SoundMuffler'
import TouchControls from './react/TouchControls'
import widgets from './react/widgets'
@@ -62,6 +63,7 @@ const InGameUi = () => {
+