fix: fix crashes on packets logging recording

fix: make replay panel minmizable
This commit is contained in:
Vitaly Turovsky 2025-03-03 15:31:25 +03:00
commit b0da1e41d6
9 changed files with 114 additions and 57 deletions

View file

@ -81,7 +81,7 @@
"mojangson": "^2.0.4",
"net-browserify": "github:zardoy/prismarinejs-net-browserify",
"node-gzip": "^1.1.2",
"mcraft-fun-mineflayer": "^0.1.7",
"mcraft-fun-mineflayer": "^0.1.8",
"peerjs": "^1.5.0",
"pixelarticons": "^1.8.1",
"pretty-bytes": "^6.1.1",
@ -145,7 +145,7 @@
"http-browserify": "^1.7.0",
"http-server": "^14.1.1",
"https-browserify": "^1.0.0",
"mineflayer-mouse": "^0.0.4",
"mineflayer-mouse": "^0.0.5",
"mc-assets": "^0.2.37",
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
"mineflayer": "github:zardoy/mineflayer",

22
pnpm-lock.yaml generated
View file

@ -135,8 +135,8 @@ importers:
specifier: ^4.17.21
version: 4.17.21
mcraft-fun-mineflayer:
specifier: ^0.1.7
version: 0.1.7(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13))
specifier: ^0.1.8
version: 0.1.8(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13))
minecraft-data:
specifier: 3.83.1
version: 3.83.1
@ -359,8 +359,8 @@ importers:
specifier: github:zardoy/mineflayer
version: https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)
mineflayer-mouse:
specifier: ^0.0.4
version: 0.0.4(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1)
specifier: ^0.0.5
version: 0.0.5(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1)
mineflayer-pathfinder:
specifier: ^2.4.4
version: 2.4.4
@ -6575,9 +6575,9 @@ packages:
resolution: {integrity: sha512-49tk3shwxsDoV0PrrbORZEKg613vUQPULgusWjXNl8JEma5y41LEo57D6q4aHliXsV3Gb9ThrkFf6hIb0WlY1Q==}
engines: {node: '>=18.0.0'}
mcraft-fun-mineflayer@0.1.7:
resolution: {integrity: sha512-DJPpp1YFwztoscdIOwfqBV9lbotva621F9GEep3BlqG3l06UdTzX2ElkvwyTR0IurFFBX/YKsNfxwL5WtLytgw==}
version: 0.1.7
mcraft-fun-mineflayer@0.1.8:
resolution: {integrity: sha512-jyJTihNHfeToBPwVs3QMKBlVcaCABJ25YN2eoIBQEVTRVFzaXh13XRpElphLzTMj1Q5XFYqufHtMoR4tsb08qQ==}
version: 0.1.8
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
peerDependencies:
'@roamhq/wrtc': '*'
@ -6801,8 +6801,8 @@ packages:
resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824}
version: 1.2.0
mineflayer-mouse@0.0.4:
resolution: {integrity: sha512-55GqQhJWCXnOnm30uOjtI7nsawPb0kA3cAv6a5n1NJjTWFR6hzMkiRT6xGLYrvYhdf6Er3nsE2Ok/Aysa/jtFQ==}
mineflayer-mouse@0.0.5:
resolution: {integrity: sha512-0r/AOGTq+wZH9vrBcW93jH2dGRSlwlO6xc1Z67VJUFlZZ8oBefAOpiZq7LIGc7ROVbpcKEKjROdNv/iCFmzXYA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
mineflayer-pathfinder@2.4.4:
@ -17372,7 +17372,7 @@ snapshots:
apl-image-packer: 1.1.0
zod: 3.24.1
mcraft-fun-mineflayer@0.1.7(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)):
mcraft-fun-mineflayer@0.1.8(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/748163e536abe94f3dc8ada7a542bcd689bbbf49(encoding@0.1.13)):
dependencies:
'@zardoy/flying-squid': 0.0.49(encoding@0.1.13)
exit-hook: 2.2.1
@ -17742,7 +17742,7 @@ snapshots:
- encoding
- supports-color
mineflayer-mouse@0.0.4(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1):
mineflayer-mouse@0.0.5(@types/debug@4.1.12)(@types/node@22.8.1)(terser@5.31.3)(tsx@4.7.0)(yaml@2.4.1):
dependencies:
change-case: 5.4.4
debug: 4.4.0(supports-color@8.1.1)

View file

@ -63,7 +63,8 @@ type AppQsParamsArrayTransformed = {
[k in keyof AppQsParamsArray]: string[]
}
const initialAppConfig = process.env.INLINED_APP_CONFIG as AppConfig ?? {}
globalThis.process ??= {} as any
const initialAppConfig = process?.env?.INLINED_APP_CONFIG as AppConfig ?? {}
export const appQueryParams = new Proxy<AppQsParams>({} as AppQsParams, {
get (target, property) {

View file

@ -138,6 +138,7 @@ export const createFullScreenProgressReporter = (): ProgressReporter => {
setLoadingScreenStatus(message)
},
end () {
if (appStatusState.isError) return
fullScreenReporters.splice(fullScreenReporters.indexOf(reporter), 1)
if (fullScreenReporters.length === 0) {
setLoadingScreenStatus(undefined)

View file

@ -322,6 +322,7 @@ export async function connect (connectOptions: ConnectOptions) {
if (ended) return
ended = true
viewer.resetAll()
progress.end()
localServer = window.localServer = window.server = undefined
gameAdditionalState.viewerConnection = false
@ -692,6 +693,7 @@ export async function connect (connectOptions: ConnectOptions) {
} catch (err) {
handleError(err)
}
if (!bot) return
if (connectOptions.server) {
bot.loadPlugin(ping)
@ -700,7 +702,6 @@ export async function connect (connectOptions: ConnectOptions) {
if (!localReplaySession) {
bot.loadPlugin(localRelayServerPlugin)
}
if (!bot) return
const p2pConnectTimeout = p2pMultiplayer ? setTimeout(() => { throw new UserError('Spawn timeout. There might be error on the other side, check console.') }, 20_000) : undefined

View file

@ -8,7 +8,7 @@ import { appQueryParamsArray } from './appParams'
import type { AppConfig } from './globalState'
const isDev = process.env.NODE_ENV === 'development'
const initialAppConfig = process.env.INLINED_APP_CONFIG as AppConfig ?? {}
const initialAppConfig = process.env?.INLINED_APP_CONFIG as AppConfig ?? {}
const defaultOptions = {
renderDistance: 3,
keepChunksDistance: 1,

View file

@ -232,14 +232,15 @@ export default () => {
if (pauseLinksConfig) {
for (const [i, row] of pauseLinksConfig.entries()) {
const rowButtons: React.ReactNode[] = []
for (const button of row) {
for (const [k, button] of row.entries()) {
const key = `${i}-${k}`
const style = { width: (204 / row.length - (row.length > 1 ? 4 : 0)) + 'px' }
if (button.type === 'discord') {
rowButtons.push(<DiscordButton key={i} style={style} text={button.text}/>)
rowButtons.push(<DiscordButton key={key} style={style} text={button.text}/>)
} else if (button.type === 'github') {
rowButtons.push(<Button key={i} className="button" style={style} onClick={() => openGithub()}>{button.text ?? 'GitHub'}</Button>)
rowButtons.push(<Button key={key} className="button" style={style} onClick={() => openGithub()}>{button.text ?? 'GitHub'}</Button>)
} else if (button.type === 'url' && button.text) {
rowButtons.push(<Button key={i} className="button" style={style} onClick={() => openURL(button.url)}>{button.text}</Button>)
rowButtons.push(<Button key={key} className="button" style={style} onClick={() => openURL(button.url)}>{button.text}</Button>)
}
}
pauseLinks.push(<div className={styles.row}>{rowButtons}</div>)

View file

@ -41,31 +41,100 @@ export default function ReplayPanel ({
style
}: Props) {
const [filter, setFilter] = useState(defaultFilter)
const [isMinimized, setIsMinimized] = useState(false)
const { filtered: filteredPackets, hiddenCount } = filterPackets(packets.slice(-500), filter)
useEffect(() => {
onFilterChange(filter)
}, [filter, onFilterChange])
const handlePlayPauseClick = () => {
if (isMinimized) {
setIsMinimized(false)
} else {
onPlayPause?.(!isPlaying)
}
}
const playPauseButton = (
<button
onClick={handlePlayPauseClick}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: '4px',
color: DARK_COLORS.text
}}
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
{isPlaying ? (
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
) : (
<path d="M8 5v14l11-7z"/>
)}
</svg>
</button>
)
const baseContainerStyle = {
position: 'fixed',
top: 18,
right: 0,
zIndex: 1000,
background: DARK_COLORS.bg,
padding: '16px',
borderRadius: '0 0 8px 0',
boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
display: 'flex',
flexDirection: 'column',
gap: '12px',
color: DARK_COLORS.text,
...style
} as const
if (isMinimized) {
return (
<div style={{
...baseContainerStyle,
width: 'auto'
}}>
{playPauseButton}
</div>
)
}
return (
<div style={{
position: 'fixed',
top: 18,
right: 0,
zIndex: 1000,
background: DARK_COLORS.bg,
padding: '16px',
borderRadius: '0 0 8px 0',
boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
...baseContainerStyle,
width: '400px',
maxHeight: '80vh',
display: 'flex',
flexDirection: 'column',
gap: '12px',
color: DARK_COLORS.text,
...style
maxHeight: '80vh'
}}>
<div style={{ fontSize: '12px', fontWeight: 'bold' }}>{replayName || 'Unnamed Replay'}</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div style={{ fontSize: '12px', fontWeight: 'bold' }}>{replayName || 'Unnamed Replay'}</div>
<button
onClick={() => setIsMinimized(true)}
style={{
background: 'none',
border: 'none',
color: DARK_COLORS.text,
cursor: 'pointer',
padding: '4px',
fontSize: '14px',
opacity: 0.7,
transition: 'opacity 0.2s'
}}
onMouseEnter={e => {
e.currentTarget.style.opacity = '1'
}}
onMouseLeave={e => {
e.currentTarget.style.opacity = '0.7'
}}
>
</button>
</div>
<div style={{ fontSize: '8px', color: '#888888', marginTop: '-8px' }}>Integrated server emulation. Testing client...</div>
<FilterInput
@ -85,25 +154,7 @@ export default function ReplayPanel ({
/>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<button
onClick={() => onPlayPause?.(!isPlaying)}
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: '4px',
color: DARK_COLORS.text
}}
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
{isPlaying ? (
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
) : (
<path d="M8 5v14l11-7z"/>
)}
</svg>
</button>
{playPauseButton}
<ProgressBar current={progress.current} total={progress.total} />
</div>

View file

@ -1,4 +1,5 @@
import { useRef, useState } from 'react'
import { processPacketDataForLogging } from 'mcraft-fun-mineflayer/build/packetsLogger'
import { PacketData } from '../../ReplayPanel'
import { useScrollBehavior } from '../../hooks/useScrollBehavior'
import { ClientOnMap } from '../../../generatedServerPackets'
@ -12,6 +13,7 @@ const formatters: Record<string, (data: any) => string> = {
const blockEntitiesCount = data.blockEntities?.length
return `x:${data.x} z:${data.z} C:${sizeOfChunk} E:${blockEntitiesCount}`
},
default: (data) => processPacketDataForLogging(data)
}
const getPacketIcon = (name: string): string => {
@ -115,7 +117,7 @@ export default function PacketList ({ packets, filter, maxHeight = 300 }: Props)
{packet.name}
</span>
<span style={{ color: DARK_COLORS.textDim, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis' }}>
{formatters[packet.name]?.(packet.data) ?? JSON.stringify(packet.data)}
{formatters[packet.name]?.(packet.data) ?? formatters.default(packet.data)}
</span>
</div>
{expandedPacket === packet.position && (
@ -123,14 +125,14 @@ export default function PacketList ({ packets, filter, maxHeight = 300 }: Props)
<div style={{ marginBottom: '8px' }}>
<strong>Data:</strong>
<pre style={{ margin: '4px 0', color: DARK_COLORS.textDim }}>
{JSON.stringify(packet.data, null, 2)}
{JSON.stringify(JSON.parse(formatters.default(packet.data)), null, 2)}
</pre>
</div>
{packet.actualVersion && (
<div>
<strong>Actual Version:</strong>
<pre style={{ margin: '4px 0', color: DARK_COLORS.textDim }}>
{JSON.stringify(packet.actualVersion, null, 2)}
{JSON.stringify(JSON.parse(formatters.default(packet.actualVersion)), null, 2)}
</pre>
</div>
)}