No big features yet (#52)

This commit is contained in:
Vitaly 2024-01-10 06:33:44 +05:30 committed by GitHub
commit 59b689d931
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 143 additions and 72 deletions

View file

@ -19,6 +19,7 @@ jobs:
- run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
# will install + build to .vercel/output/static
- run: vercel build --token=${{ secrets.VERCEL_TOKEN }} --prod
- run: pnpm build-storybook
- name: Copy playground files
run: node prismarine-viewer/esbuild.mjs && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js
- name: Download Generated Sounds map

View file

@ -15,7 +15,7 @@
"test-mc-server": "tsx cypress/minecraft-server.mjs",
"lint": "eslint \"{src,cypress}/**/*.{ts,js,jsx,tsx}\"",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"build-storybook": "storybook build && node scripts/build.js moveStorybookFiles",
"start-experiments": "vite --config experiments/vite.config.ts",
"watch-worker": "node prismarine-viewer/buildWorker.mjs -w"
},
@ -98,7 +98,7 @@
"http-server": "^14.1.1",
"https-browserify": "^1.0.0",
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
"mineflayer": "github:zardoy/mineflayer#custom",
"mineflayer": "github:PrismarineJS/mineflayer",
"mineflayer-pathfinder": "^2.4.4",
"npm-run-all": "^4.1.5",
"os-browserify": "^0.3.0",

60
pnpm-lock.yaml generated
View file

@ -226,8 +226,8 @@ importers:
specifier: github:zardoy/minecraft-inventory-gui#next
version: github.com/zardoy/minecraft-inventory-gui/69003692b3041d94a420a65c7d3cc1b37737e838(@types/react@18.2.20)(react@18.2.0)
mineflayer:
specifier: github:zardoy/mineflayer#custom
version: github.com/zardoy/mineflayer/e828c161aab120f2d926fba48de3b4d57c361710
specifier: github:PrismarineJS/mineflayer
version: github.com/PrismarineJS/mineflayer/5c71edf48bb2f2dfa16cddb9af5baa0c4d55cf0d
mineflayer-pathfinder:
specifier: ^2.4.4
version: 2.4.4
@ -14846,6 +14846,34 @@ packages:
react: 18.2.0
dev: false
github.com/PrismarineJS/mineflayer/5c71edf48bb2f2dfa16cddb9af5baa0c4d55cf0d:
resolution: {tarball: https://codeload.github.com/PrismarineJS/mineflayer/tar.gz/5c71edf48bb2f2dfa16cddb9af5baa0c4d55cf0d}
name: mineflayer
version: 4.17.0
engines: {node: '>=18'}
dependencies:
minecraft-data: 3.58.0
minecraft-protocol: github.com/zardoy/minecraft-protocol/436e0f2945d82408cfd1eb4262535c205bcba8d0
prismarine-biome: 1.3.0(minecraft-data@3.58.0)(prismarine-registry@1.7.0)
prismarine-block: github.com/zardoy/prismarine-block/00cd810ca6853024b2e73ff0d405d1b1e397defc
prismarine-chat: 1.9.1
prismarine-chunk: 1.35.0(minecraft-data@3.58.0)
prismarine-entity: 2.3.1
prismarine-item: 1.14.0
prismarine-nbt: 2.2.1
prismarine-physics: 1.8.0
prismarine-recipe: 1.3.1(prismarine-registry@1.7.0)
prismarine-registry: 1.7.0
prismarine-windows: 2.8.0
prismarine-world: github.com/zardoy/prismarine-world/c358222204d21fe7d45379fbfcefb047f926c786
protodef: 1.15.0
typed-emitter: 1.4.0
vec3: 0.1.8
transitivePeerDependencies:
- encoding
- supports-color
dev: true
github.com/PrismarineJS/node-process/380d0b4f4c86f1b65b216c311bf00431f314e88e:
resolution: {tarball: https://codeload.github.com/PrismarineJS/node-process/tar.gz/380d0b4f4c86f1b65b216c311bf00431f314e88e}
name: process
@ -14912,34 +14940,6 @@ packages:
- encoding
- supports-color
github.com/zardoy/mineflayer/e828c161aab120f2d926fba48de3b4d57c361710:
resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/e828c161aab120f2d926fba48de3b4d57c361710}
name: mineflayer
version: 4.14.0
engines: {node: '>=14'}
dependencies:
minecraft-data: 3.58.0
minecraft-protocol: github.com/zardoy/minecraft-protocol/436e0f2945d82408cfd1eb4262535c205bcba8d0
prismarine-biome: 1.3.0(minecraft-data@3.58.0)(prismarine-registry@1.7.0)
prismarine-block: github.com/zardoy/prismarine-block/00cd810ca6853024b2e73ff0d405d1b1e397defc
prismarine-chat: 1.9.1
prismarine-chunk: 1.35.0(minecraft-data@3.58.0)
prismarine-entity: 2.3.1
prismarine-item: 1.14.0
prismarine-nbt: 2.2.1
prismarine-physics: 1.8.0
prismarine-recipe: 1.3.1(prismarine-registry@1.7.0)
prismarine-registry: 1.7.0
prismarine-windows: 2.8.0
prismarine-world: github.com/zardoy/prismarine-world/c358222204d21fe7d45379fbfcefb047f926c786
protodef: 1.15.0
typed-emitter: 1.4.0
vec3: 0.1.8
transitivePeerDependencies:
- encoding
- supports-color
dev: true
github.com/zardoy/prismarine-block/00cd810ca6853024b2e73ff0d405d1b1e397defc:
resolution: {tarball: https://codeload.github.com/zardoy/prismarine-block/tar.gz/00cd810ca6853024b2e73ff0d405d1b1e397defc}
name: prismarine-block

View file

@ -91,6 +91,10 @@ exports.getSwAdditionalEntries = () => {
return output
}
exports.moveStorybookFiles = () => {
fs.renameSync('storybook-static', 'dist/storybook')
}
const fn = require.main === module && exports[process.argv[2]]
if (fn) {

View file

@ -1,8 +1,8 @@
// this should actually be moved to mineflayer / prismarine-viewer
import { fromFormattedString } from '@xmcl/text-component'
import { fromFormattedString, TextComponent } from '@xmcl/text-component'
export type MessageFormatPart = {
export type MessageFormatPart = Pick<TextComponent, 'hoverEvent' | 'clickEvent'> & {
text: string
color?: string
bold?: boolean

View file

@ -10,7 +10,7 @@ import { isGameActive, showModal, gameAdditionalState, activeModalStack, hideCur
import { goFullscreen, pointerLock, reloadChunks } from './utils'
import { options } from './optionsStorage'
import { openPlayerInventory } from './playerWindows'
import { initialChatOpenValue } from './react/ChatContainer'
import { chatInputValueGlobal } from './react/ChatContainer'
import { fsState } from './loadSave'
// doesnt seem to work for now
@ -226,7 +226,7 @@ contro.on('trigger', ({ command }) => {
showModal({ reactType: 'chat' })
break
case 'general.command':
initialChatOpenValue.value = '/'
chatInputValueGlobal.value = '/'
showModal({ reactType: 'chat' })
break
case 'general.selectItem':

View file

@ -187,6 +187,9 @@ export const showNotification = (newNotification: Partial<typeof notification>)
// todo restore auto-save on interval for player data! (or implement it in flying squid since there is already auto-save for world)
window.addEventListener('unload', (e) => {
if (!window.justReloaded) {
sessionStorage.justReloaded = false
}
void saveServer()
})
@ -201,6 +204,10 @@ window.inspectPlayer = () => require('fs').promises.readFile('/world/playerdata/
// todo move from global state
window.addEventListener('beforeunload', (event) => {
if (!window.justReloaded) {
sessionStorage.justReloaded = false
}
// todo-low maybe exclude chat?
if (!isGameActive(true) && activeModalStack.at(-1)?.elem?.id !== 'chat') return
if (sessionStorage.lastReload && !options.preventDevReloadWhilePlaying) return

View file

@ -433,6 +433,7 @@ async function connect (connectOptions: {
noPongTimeout: 240 * 1000,
closeTimeout: 240 * 1000,
respawn: options.autoRespawn,
maxCatchupTicks: 0,
async versionSelectedHook (client) {
// todo keep in sync with esbuild preload, expose cache ideally
if (client.version === '1.20.1') {
@ -447,12 +448,12 @@ async function connect (connectOptions: {
if (singleplayer || p2pMultiplayer) {
// in case of p2pMultiplayer there is still flying-squid on the host side
const _supportFeature = bot.supportFeature
bot.supportFeature = (feature) => {
bot.supportFeature = ((feature) => {
if (unsupportedLocalServerFeatures.includes(feature)) {
return false
}
return _supportFeature(feature)
}
}) as typeof bot.supportFeature
bot.emit('inject_allowed')
bot._client.emit('connect')

View file

@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'
import { useEffect, useState } from 'react'
import { formatMessage } from '../botUtils'
import Chat, { fadeMessage, initialChatOpenValue } from './ChatContainer'
import Chat, { fadeMessage, chatInputValueGlobal } from './ChatContainer'
import Button from './Button'
window.spamMessage = window.spamMessage ?? ''
@ -20,7 +20,7 @@ const meta: Meta<typeof Chat> = {
const abortController = new AbortController()
addEventListener('keyup', (e) => {
if (e.code === 'KeyY') {
initialChatOpenValue.value = '/'
chatInputValueGlobal.value = '/'
setOpen(true)
e.stopImmediatePropagation()
}
@ -103,7 +103,6 @@ export const Primary: Story = {
'underlined': false,
'strikethrough': false,
'obfuscated': false,
//@ts-expect-error
'json': {
'insertion': 'pviewer672',
'clickEvent': {
@ -129,6 +128,7 @@ export const Primary: Story = {
},
'hoverEvent': {
'action': 'show_entity',
//@ts-expect-error
'contents': {
'type': 'minecraft:player',
'id': 'ecd0eeb1-625e-3fea-b16e-cb449dcfa434',

View file

@ -1,4 +1,5 @@
import { useUsingTouch } from '@dimaka/interface'
import { proxy, subscribe } from 'valtio'
import { useEffect, useMemo, useRef, useState } from 'react'
import { isCypress } from '../standaloneUtils'
import { MessageFormatPart } from '../botUtils'
@ -12,7 +13,7 @@ export type Message = {
faded?: boolean
}
const MessageLine = ({ message }) => {
const MessageLine = ({ message }: {message: Message}) => {
const classes = {
'chat-message-fadeout': message.fading,
'chat-message-fade': message.fading,
@ -35,9 +36,9 @@ type Props = {
// width?: number
}
export const initialChatOpenValue = {
export const chatInputValueGlobal = proxy({
value: ''
}
})
export const fadeMessage = (message: Message, initialTimeout: boolean, requestUpdate: () => void) => {
setTimeout(() => {
@ -100,11 +101,18 @@ export default ({ messages, opacity = 1, fetchCompletionItems, opened, sendMessa
useEffect(() => {
if (opened) {
updateInputValue(initialChatOpenValue.value)
initialChatOpenValue.value = ''
updateInputValue(chatInputValueGlobal.value)
chatInputValueGlobal.value = ''
if (!usingTouch) {
chatInput.current.focus()
}
const unsubscribe = subscribe(chatInputValueGlobal, () => {
if (!chatInputValueGlobal.value) return
updateInputValue(chatInputValueGlobal.value)
chatInputValueGlobal.value = ''
chatInput.current.focus()
})
return unsubscribe
}
if (!opened && chatMessages.current) {
chatMessages.current.scrollTop = chatMessages.current.scrollHeight

View file

@ -4,7 +4,7 @@ import MessageFormatted from './MessageFormatted'
import Button from './Button'
type Props = {
dieReasonMessage: readonly MessageFormatPart[]
dieReasonMessage: MessageFormatPart[]
respawnCallback: () => void
disconnectCallback: () => void
}

View file

@ -56,7 +56,7 @@ export default () => {
if (!isModalActive || !dieReasonMessage || options.autoRespawn) return null
return <DeathScreen
dieReasonMessage={dieReasonMessage}
dieReasonMessage={dieReasonMessage as MessageFormatPart[]}
respawnCallback={() => {
bot._client.write('client_command', bot.supportFeature('respawnIsPayload') ? { payload: 0 } : { actionId: 0 })
}}

View file

@ -20,6 +20,8 @@ interface Props {
const refreshApp = async () => {
const registration = await navigator.serviceWorker.getRegistration()
await registration?.unregister()
window.justReloaded = true
sessionStorage.justReloaded = true
window.location.reload()
}
@ -36,7 +38,13 @@ export default ({ connectToServerAction, mapsProvider, singleplayerAction, optio
fetch('./version.txt').then(async (f) => {
if (f.status === 404) return
const contents = await f.text()
setVersionStatus(`(${contents === process.env.BUILD_VERSION ? 'latest' : 'new version available'})`)
const isLatest = contents === process.env.BUILD_VERSION
if (!isLatest && sessionStorage.justReloaded) {
// try to force bypass cache
location.search = '?update=true'
}
sessionStorage.justReloaded = false
setVersionStatus(`(${isLatest ? 'latest' : 'new version available'})`)
setVersionTitle(`Loaded: ${process.env.BUILD_VERSION}. Remote: ${contents}`)
}, () => { })
}
@ -114,7 +122,10 @@ export default ({ connectToServerAction, mapsProvider, singleplayerAction, optio
<div className={styles['bottom-info']}>
<span
title={`${versionTitle} (click to reload)`}
onClick={refreshApp}
onClick={async () => {
setVersionStatus('(reloading)')
await refreshApp()
}}
className={styles['product-info']}
>
Prismarine Web Client {versionStatus}

View file

@ -1,8 +1,63 @@
import { ComponentProps } from 'react'
import { render } from '@xmcl/text-component'
import { noCase } from 'change-case'
import { MessageFormatPart } from '../botUtils'
import { openURL } from '../menus/components/common'
import { chatInputValueGlobal } from './ChatContainer'
const hoverItemToText = (hoverEvent: MessageFormatPart['hoverEvent']) => {
if (!hoverEvent) return undefined
const contents = hoverEvent['contents'] ?? hoverEvent.value
if (typeof contents === 'string') return contents
// if (hoverEvent.action === 'show_text') {
// return contents
// }
if (hoverEvent.action === 'show_item') {
return contents.id
}
if (hoverEvent.action === 'show_entity') {
let str = noCase(contents.type.replace('minecraft:', ''))
if (contents.name) str += `: ${contents.name.text}`
return str
}
}
const clickEventToProps = (clickEvent: MessageFormatPart['clickEvent']) => {
if (!clickEvent) return
if (clickEvent.action === 'run_command' || clickEvent.action === 'suggest_command') {
return {
onClick () {
chatInputValueGlobal.value = clickEvent.value
}
}
}
if (clickEvent.action === 'open_url') {
return {
onClick () {
const confirm = window.confirm(`Open ${clickEvent.value}?`)
if (confirm) {
openURL(clickEvent.value)
}
}
}
}
//@ts-expect-error todo
if (clickEvent.action === 'copy_to_clipboard') {
return {
onClick () {
navigator.clipboard.writeText(clickEvent.value)
}
}
}
}
export const MessagePart = ({ part, ...props }: { part: MessageFormatPart } & ComponentProps<'span'>) => {
const { color, italic, bold, underlined, strikethrough, text } = part
const { color, italic, bold, underlined, strikethrough, text, clickEvent, hoverEvent, obfuscated } = part
const clickProps = clickEventToProps(clickEvent)
const hoverMessageRaw = hoverItemToText(hoverEvent)
const hoverItemText = hoverMessageRaw && typeof hoverMessageRaw !== 'string' ? render(hoverMessageRaw).children.map(child => child.component.text).join('') : hoverMessageRaw
const applyStyles = [
color ? colorF(color.toLowerCase()) + `; text-shadow: 1px 1px 0px ${getColorShadow(colorF(color.toLowerCase()).replace('color:', ''))}` : messageFormatStylesMap.white,
@ -10,10 +65,11 @@ export const MessagePart = ({ part, ...props }: { part: MessageFormatPart } & Co
bold && messageFormatStylesMap.bold,
italic && messageFormatStylesMap.italic,
underlined && messageFormatStylesMap.underlined,
strikethrough && messageFormatStylesMap.strikethrough
strikethrough && messageFormatStylesMap.strikethrough,
obfuscated && messageFormatStylesMap.obfuscated
].filter(Boolean)
return <span style={parseInlineStyle(applyStyles.join(' '))} {...props}>{text}</span>
return <span title={hoverItemText} style={parseInlineStyle(applyStyles.join(' '))} {...clickProps} {...props}>{text}</span>
}
export default ({ parts }: { parts: readonly MessageFormatPart[] }) => {
@ -69,5 +125,6 @@ export const messageFormatStylesMap = {
bold: 'font-weight:900',
strikethrough: 'text-decoration:line-through',
underlined: 'text-decoration:underline',
italic: 'font-style:italic'
italic: 'font-style:italic',
obfuscated: 'color: #222326;background-color: #222326;'
}

View file

@ -1,18 +0,0 @@
import type { Meta, StoryObj } from '@storybook/react'
import OptionsGroup from './OptionsGroup'
const meta: Meta<typeof OptionsGroup> = {
component: OptionsGroup,
// render: () => <OptionsGroup />
}
export default meta
type Story = StoryObj<typeof OptionsGroup>;
export const Primary: Story = {
args: {
group: 'controls',
backButtonAction () { }
},
}