feat: publish all stories UI components to npm! (#111)
This commit is contained in:
parent
ab9e5db445
commit
1e7153c2e2
68 changed files with 688 additions and 289 deletions
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
|
|
@ -18,6 +18,7 @@ jobs:
|
|||
- run: pnpm check-build
|
||||
- run: pnpm test-unit
|
||||
- run: pnpm lint
|
||||
- run: pnpm tsx scripts/buildNpmReact.ts
|
||||
- run: nohup pnpm prod-start &
|
||||
- run: nohup pnpm test-mc-server &
|
||||
- uses: cypress-io/github-action@v5
|
||||
|
|
|
|||
7
.github/workflows/publish.yml
vendored
7
.github/workflows/publish.yml
vendored
|
|
@ -33,9 +33,16 @@ jobs:
|
|||
pnpx zardoy-release node --footer "This release URL: ${{ steps.deploy.outputs.stdout }}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- run: |
|
||||
pnpx zardoy-release npm
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- run: cp vercel.json .vercel/output/static/vercel.json
|
||||
- uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: .vercel/output/static
|
||||
force_orphan: true
|
||||
- run: pnpm tsx scripts/buildNpmReact.ts
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
|
|
|||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -7,7 +7,7 @@ package-lock.json
|
|||
Thumbs.db
|
||||
build
|
||||
localSettings.mjs
|
||||
dist
|
||||
dist*
|
||||
.DS_Store
|
||||
.idea/
|
||||
world
|
||||
|
|
@ -17,3 +17,5 @@ out
|
|||
.vercel
|
||||
generated
|
||||
storybook-static
|
||||
|
||||
src/react/npmReactComponents.ts
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
A true Minecraft client running in your browser! A port of the original game to the web, written in JavaScript using modern web technologies.
|
||||
|
||||
This project is a work in progress, but I consider it to be usable. If you encounter any bugs or usability issues, please report them!
|
||||
If you encounter any bugs or usability issues, please report them!
|
||||
|
||||
You can try this out at [mcraft.fun](https://mcraft.fun/), [pcm.gg](https://pcm.gg) (short link) [mcon.vercel.app](https://mcon.vercel.app/) or the GitHub pages deploy. Every commit from the `develop` (default) branch is deployed to [s.mcraft.fun](https://s.mcraft.fun/) - so it's usually newer, but might be less stable.
|
||||
|
||||
|
|
@ -19,6 +19,8 @@ You can try this out at [mcraft.fun](https://mcraft.fun/), [pcm.gg](https://pcm.
|
|||
- Resource pack support
|
||||
- even even more!
|
||||
|
||||
All components that are in [Storybook](https://mcraft.fun/storybook) are published as npm module and can be used in other projects: [`minecraft-react`](https://npmjs.com/minecraft-react)
|
||||
|
||||
### Recommended Settings
|
||||
|
||||
- Controls -> **Raw Input** -> **On** - This will make the controls more precise
|
||||
|
|
@ -65,7 +67,6 @@ To open the console, press `F12`, or if you are on mobile, you can type `#debug`
|
|||
|
||||
It should be easy to build/start the project locally. See [CONTRIBUTING.MD](./CONTRIBUTING.md) for more info.
|
||||
|
||||
There is storybook for fast UI development. Run `pnpm storybook` to start it.
|
||||
There is world renderer playground ([link](https://mcon.vercel.app/playground.html)).
|
||||
|
||||
However, there are many things that can be done in online version. You can access some global variables in the console and useful examples:
|
||||
|
|
|
|||
32
README.NPM.MD
Normal file
32
README.NPM.MD
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Minecraft React
|
||||
|
||||
```bash
|
||||
yarn add minecraft-react
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
import { Scoreboard } from 'minecraft-react'
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Scoreboard
|
||||
open
|
||||
title="Scoreboard"
|
||||
items={[
|
||||
{ name: 'Player 1', value: 10 },
|
||||
{ name: 'Player 2', value: 20 },
|
||||
{ name: 'Player 3', value: 30 },
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
See [Storybook](https://mcraft.fun/storybook/) or [Storybook (Mirror link)](https://mcon.vercel.app/storybook/) for more examples and full components list. Also take a look at the full [standalone example](https://github.com/zardoy/prismarine-web-client/tree/experiments/UiStandaloneExample.tsx).
|
||||
|
||||
There are two types of components:
|
||||
|
||||
- Small UI components or HUD components
|
||||
- Full screen components (like sign editor, worlds selector)
|
||||
71
experiments/UiStandaloneExample.tsx
Normal file
71
experiments/UiStandaloneExample.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import React, { useState } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import {
|
||||
Button,
|
||||
Slider,
|
||||
ArmorBar,
|
||||
BreathBar,
|
||||
Chat,
|
||||
HealthBar,
|
||||
PlayerListOverlay,
|
||||
Scoreboard,
|
||||
MessageFormattedString,
|
||||
XPBar,
|
||||
FoodBar
|
||||
} from '../dist-npm'
|
||||
|
||||
const ExampleDemo = () => {
|
||||
const [sliderValue, setSliderValue] = useState(0)
|
||||
|
||||
return (
|
||||
<div style={{ scale: '2', transformOrigin: 'top left', width: '50%', height: '50dvh', fontFamily: 'mojangles, sans-serif', background: 'gray' }}>
|
||||
<Button>Button</Button>
|
||||
<Slider label="Slider" value={sliderValue} updateValue={value => setSliderValue(value)} />
|
||||
<ArmorBar armorValue={10} />
|
||||
<Chat
|
||||
messages={[
|
||||
{ id: 0, parts: [{ text: 'A formmated message in the chat', color: 'blue' }] },
|
||||
{ id: 1, parts: [{ text: 'An other message in the chat', color: 'red' }] },
|
||||
]}
|
||||
usingTouch={false}
|
||||
opened
|
||||
sendMessage={message => {
|
||||
console.log('typed', message)
|
||||
// close
|
||||
}}
|
||||
/>
|
||||
<BreathBar oxygen={10} />
|
||||
<HealthBar isHardcore={false} healthValue={10} damaged={false} />
|
||||
<FoodBar food={10} />
|
||||
<PlayerListOverlay
|
||||
style={{
|
||||
position: 'static',
|
||||
}}
|
||||
clientId="" // needed for current player highlight
|
||||
serverIP="Server IP"
|
||||
tablistHeader="Tab §aHeader"
|
||||
tablistFooter="Tab §bFooter"
|
||||
playersLists={[
|
||||
[
|
||||
{ username: 'Player 1', ping: 10, uuid: undefined },
|
||||
{ username: 'Player 2', ping: 20, uuid: undefined },
|
||||
{ username: 'Player 3', ping: 30, uuid: undefined },
|
||||
],
|
||||
]}
|
||||
/>
|
||||
"§bRed" displays as <MessageFormattedString message="§bRed" />
|
||||
<Scoreboard
|
||||
open
|
||||
title="Scoreboard"
|
||||
items={[
|
||||
{ name: 'Player 1', value: 10 },
|
||||
{ name: 'Player 2', value: 20 },
|
||||
{ name: 'Player 3', value: 30 },
|
||||
]}
|
||||
/>
|
||||
<XPBar gamemode="survival" level={10} progress={0.5} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
createRoot(document.body as Element).render(<ExampleDemo />)
|
||||
14
package.json
14
package.json
|
|
@ -29,7 +29,12 @@
|
|||
"web",
|
||||
"client"
|
||||
],
|
||||
"author": "PrismarineJS",
|
||||
"publish": {
|
||||
"preset": {
|
||||
"publishOnlyIfChanged": true,
|
||||
"runBuild": false
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@dimaka/interface": "0.0.3-alpha.0",
|
||||
|
|
@ -48,6 +53,7 @@
|
|||
"adm-zip": "^0.5.12",
|
||||
"browserfs": "github:zardoy/browserfs#build",
|
||||
"change-case": "^5.1.2",
|
||||
"classnames": "^2.5.1",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"cypress-plugin-snapshots": "^1.4.4",
|
||||
|
|
@ -78,11 +84,15 @@
|
|||
"react-dom": "^18.2.0",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"remark": "^15.0.1",
|
||||
"filesize": "^10.0.12",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"skinview3d": "^3.0.1",
|
||||
"source-map-js": "^1.0.2",
|
||||
"stats-gl": "^1.0.5",
|
||||
"stats.js": "^0.17.0",
|
||||
"use-typed-event-listener": "^4.0.2",
|
||||
"mojangson": "^2.0.4",
|
||||
"prosemirror-menu": "^1.2.4",
|
||||
"tabbable": "^6.2.0",
|
||||
"title-case": "3.x",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
|
|
@ -113,7 +123,6 @@
|
|||
"eslint": "^8.50.0",
|
||||
"eslint-config-zardoy": "^0.2.17",
|
||||
"events": "^3.3.0",
|
||||
"filesize": "^10.0.12",
|
||||
"http-browserify": "^1.7.0",
|
||||
"http-server": "^14.1.1",
|
||||
"https-browserify": "^1.0.0",
|
||||
|
|
@ -132,7 +141,6 @@
|
|||
"three": "0.154.0",
|
||||
"timers-browserify": "^2.0.12",
|
||||
"typescript": "5.5.0-beta",
|
||||
"use-typed-event-listener": "^4.0.2",
|
||||
"vitest": "^0.34.6",
|
||||
"yaml": "^2.3.2"
|
||||
},
|
||||
|
|
|
|||
36
package.npm.json
Normal file
36
package.npm.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "minecraft-react",
|
||||
"description": "A Minecraft-like React UI library",
|
||||
"keywords": [
|
||||
"minecraft",
|
||||
"minecraft style"
|
||||
],
|
||||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"**"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./dist/react/npmReactComponents.js",
|
||||
"types": "./dist/react/npmReactComponents.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"default": "./dist/react/*",
|
||||
"types": "./dist/react/*"
|
||||
},
|
||||
"./dist": {
|
||||
"default": "./dist/*",
|
||||
"types": "./dist/*"
|
||||
}
|
||||
},
|
||||
"module": "./dist/react/npmReactComponents.js",
|
||||
"types": "./dist/react/npmReactComponents.d.ts",
|
||||
"repository": "zardoy/prismarine-web-client",
|
||||
"version": "0.0.0-dev",
|
||||
"dependencies": {},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
}
|
||||
}
|
||||
51
pnpm-lock.yaml
generated
51
pnpm-lock.yaml
generated
|
|
@ -68,6 +68,9 @@ importers:
|
|||
change-case:
|
||||
specifier: ^5.1.2
|
||||
version: 5.1.2
|
||||
classnames:
|
||||
specifier: ^2.5.1
|
||||
version: 2.5.1
|
||||
compression:
|
||||
specifier: ^1.7.4
|
||||
version: 1.7.4
|
||||
|
|
@ -92,6 +95,9 @@ importers:
|
|||
express:
|
||||
specifier: ^4.18.2
|
||||
version: 4.18.2
|
||||
filesize:
|
||||
specifier: ^10.0.12
|
||||
version: 10.0.12
|
||||
flying-squid:
|
||||
specifier: npm:@zardoy/flying-squid@^0.0.19
|
||||
version: '@zardoy/flying-squid@0.0.19(encoding@0.1.13)'
|
||||
|
|
@ -116,6 +122,9 @@ importers:
|
|||
minecraft-data:
|
||||
specifier: 3.62.0
|
||||
version: 3.62.0
|
||||
mojangson:
|
||||
specifier: ^2.0.4
|
||||
version: 2.0.4
|
||||
net-browserify:
|
||||
specifier: github:zardoy/prismarinejs-net-browserify
|
||||
version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/7d827dba61bd2f9ac9a6086fe2079a0fccadd070
|
||||
|
|
@ -137,6 +146,9 @@ importers:
|
|||
prosemirror-markdown:
|
||||
specifier: ^1.12.0
|
||||
version: 1.12.0
|
||||
prosemirror-menu:
|
||||
specifier: ^1.2.4
|
||||
version: 1.2.4
|
||||
prosemirror-state:
|
||||
specifier: ^1.4.3
|
||||
version: 1.4.3
|
||||
|
|
@ -182,6 +194,9 @@ importers:
|
|||
ua-parser-js:
|
||||
specifier: ^1.0.37
|
||||
version: 1.0.37
|
||||
use-typed-event-listener:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2(react@18.2.0)(typescript@5.5.0-beta)
|
||||
valtio:
|
||||
specifier: ^1.11.1
|
||||
version: 1.11.2(@types/react@18.2.20)(react@18.2.0)
|
||||
|
|
@ -262,9 +277,6 @@ importers:
|
|||
events:
|
||||
specifier: ^3.3.0
|
||||
version: 3.3.0
|
||||
filesize:
|
||||
specifier: ^10.0.12
|
||||
version: 10.0.12
|
||||
http-browserify:
|
||||
specifier: ^1.7.0
|
||||
version: 1.7.0
|
||||
|
|
@ -319,9 +331,6 @@ importers:
|
|||
typescript:
|
||||
specifier: 5.5.0-beta
|
||||
version: 5.5.0-beta
|
||||
use-typed-event-listener:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2(react@18.2.0)(typescript@5.5.0-beta)
|
||||
vitest:
|
||||
specifier: ^0.34.6
|
||||
version: 0.34.6(terser@5.19.2)
|
||||
|
|
@ -3635,8 +3644,8 @@ packages:
|
|||
cipher-base@1.0.4:
|
||||
resolution: {integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==}
|
||||
|
||||
classnames@2.3.2:
|
||||
resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==}
|
||||
classnames@2.5.1:
|
||||
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
|
||||
|
||||
clean-regexp@1.0.0:
|
||||
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
|
||||
|
|
@ -9968,7 +9977,7 @@ snapshots:
|
|||
'@jest/schemas': 29.6.3
|
||||
'@types/istanbul-lib-coverage': 2.0.4
|
||||
'@types/istanbul-reports': 3.0.2
|
||||
'@types/node': 20.8.0
|
||||
'@types/node': 20.12.8
|
||||
'@types/yargs': 17.0.28
|
||||
chalk: 4.1.2
|
||||
|
||||
|
|
@ -11459,11 +11468,11 @@ snapshots:
|
|||
|
||||
'@types/cors@2.8.15':
|
||||
dependencies:
|
||||
'@types/node': 20.8.0
|
||||
'@types/node': 20.12.8
|
||||
|
||||
'@types/cross-spawn@6.0.3':
|
||||
dependencies:
|
||||
'@types/node': 20.8.10
|
||||
'@types/node': 20.12.8
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
dependencies:
|
||||
|
|
@ -11512,7 +11521,7 @@ snapshots:
|
|||
|
||||
'@types/graceful-fs@4.1.7':
|
||||
dependencies:
|
||||
'@types/node': 20.8.10
|
||||
'@types/node': 20.12.8
|
||||
|
||||
'@types/http-cache-semantics@4.0.2': {}
|
||||
|
||||
|
|
@ -11621,7 +11630,7 @@ snapshots:
|
|||
|
||||
'@types/resolve@1.17.1':
|
||||
dependencies:
|
||||
'@types/node': 20.8.10
|
||||
'@types/node': 20.12.8
|
||||
|
||||
'@types/sat@0.0.31': {}
|
||||
|
||||
|
|
@ -11632,7 +11641,7 @@ snapshots:
|
|||
'@types/send@0.17.2':
|
||||
dependencies:
|
||||
'@types/mime': 1.3.3
|
||||
'@types/node': 20.8.10
|
||||
'@types/node': 20.12.8
|
||||
|
||||
'@types/serve-static@1.15.3':
|
||||
dependencies:
|
||||
|
|
@ -11941,7 +11950,7 @@ snapshots:
|
|||
|
||||
'@zardoy/react-util@0.2.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
classnames: 2.3.2
|
||||
classnames: 2.5.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
|
||||
|
|
@ -12674,7 +12683,7 @@ snapshots:
|
|||
inherits: 2.0.4
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
classnames@2.3.2: {}
|
||||
classnames@2.5.1: {}
|
||||
|
||||
clean-regexp@1.0.0:
|
||||
dependencies:
|
||||
|
|
@ -13373,7 +13382,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/cookie': 0.4.1
|
||||
'@types/cors': 2.8.15
|
||||
'@types/node': 20.8.0
|
||||
'@types/node': 20.12.8
|
||||
accepts: 1.3.8
|
||||
base64id: 2.0.0
|
||||
cookie: 0.4.2
|
||||
|
|
@ -14976,7 +14985,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
'@types/graceful-fs': 4.1.7
|
||||
'@types/node': 20.8.0
|
||||
'@types/node': 20.12.8
|
||||
anymatch: 3.1.3
|
||||
fb-watchman: 2.0.2
|
||||
graceful-fs: 4.2.11
|
||||
|
|
@ -14993,7 +15002,7 @@ snapshots:
|
|||
jest-util@29.7.0:
|
||||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 20.8.0
|
||||
'@types/node': 20.12.8
|
||||
chalk: 4.1.2
|
||||
ci-info: 3.8.0
|
||||
graceful-fs: 4.2.11
|
||||
|
|
@ -15001,13 +15010,13 @@ snapshots:
|
|||
|
||||
jest-worker@26.6.2:
|
||||
dependencies:
|
||||
'@types/node': 20.8.10
|
||||
'@types/node': 20.12.8
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
jest-worker@29.7.0:
|
||||
dependencies:
|
||||
'@types/node': 20.8.10
|
||||
'@types/node': 20.12.8
|
||||
jest-util: 29.7.0
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 8.1.1
|
||||
|
|
|
|||
148
scripts/buildNpmReact.ts
Normal file
148
scripts/buildNpmReact.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { build, transform } from 'esbuild'
|
||||
import { execSync } from 'child_process'
|
||||
// import { copy } from 'fs-extra'
|
||||
import { glob } from 'glob'
|
||||
|
||||
const isAbsolute = (path: string) => path.startsWith('/') || /^[A-Z]:/i.test(path)
|
||||
|
||||
fs.promises.readdir(path.resolve(__dirname, '../src/react')).then(async (files) => {
|
||||
const components = files
|
||||
.filter((file) => {
|
||||
if (file.startsWith('Concept')) return false
|
||||
return file.endsWith('.stories.tsx');
|
||||
})
|
||||
.map((file) => {
|
||||
return file.replace('.stories.tsx', '')
|
||||
})
|
||||
|
||||
const content = components.map((component) => {
|
||||
return `export { default as ${component} } from './${component}'`
|
||||
}).join('\n')
|
||||
|
||||
await fs.promises.writeFile(
|
||||
path.resolve(__dirname, '../src/react/npmReactComponents.ts'),
|
||||
content
|
||||
)
|
||||
|
||||
execSync('pnpm tsc -p tsconfig.npm.json', {
|
||||
cwd: path.resolve(__dirname, '../'),
|
||||
stdio: 'inherit',
|
||||
})
|
||||
|
||||
const packageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.npm.json'), 'utf-8'))
|
||||
const packageJsonRoot = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../package.json'), 'utf-8'))
|
||||
const external = Object.keys(packageJson.peerDependencies)
|
||||
const dependencies = new Set<string>()
|
||||
const version = packageJsonRoot.version
|
||||
packageJson.version = version
|
||||
|
||||
const externalize = ['minecraft-assets', 'prismarine-viewer']
|
||||
const { metafile } = await build({
|
||||
entryPoints: [path.resolve(__dirname, '../src/react/npmReactComponents.ts')],
|
||||
bundle: true,
|
||||
outfile: path.resolve(__dirname, '../dist-npm/bundle.esm.js'),
|
||||
format: 'esm',
|
||||
platform: 'browser',
|
||||
target: 'es2020',
|
||||
external: external,
|
||||
metafile: true,
|
||||
minify: true,
|
||||
write: false, // todo
|
||||
loader: {
|
||||
'.png': 'dataurl',
|
||||
},
|
||||
plugins: [
|
||||
// on external module resolve
|
||||
{
|
||||
name: 'collect-imports',
|
||||
setup (build) {
|
||||
build.onResolve({ filter: /.*/ }, (args) => {
|
||||
if (args.importer.includes('node_modules') || external.some(x => args.path.startsWith(x)) || isAbsolute(args.path)) {
|
||||
return undefined
|
||||
}
|
||||
if (args.path.startsWith('./') || args.path.startsWith('../')) {
|
||||
if (args.path.endsWith('.png') || args.path.endsWith('.css') || args.path.endsWith('.jpg') || args.path.endsWith('.jpeg')) {
|
||||
const absoluteImporting = path.join(path.dirname(args.importer), args.path)
|
||||
const absoluteRoot = path.resolve(__dirname, '../src')
|
||||
const relativeToRoot = path.relative(absoluteRoot, absoluteImporting)
|
||||
fs.copyFileSync(absoluteImporting, path.resolve(__dirname, '../dist-npm/dist-pre', relativeToRoot))
|
||||
}
|
||||
// default behavior
|
||||
return undefined
|
||||
}
|
||||
const dep = args.path.startsWith('@') ? args.path.split('/').slice(0, 2).join('/') : args.path.split('/')[0]
|
||||
if (!dependencies.has(dep)) {
|
||||
dependencies.add(dep)
|
||||
console.log('Adding dependency:', dep, 'from', args.importer)
|
||||
}
|
||||
// return { external: true }
|
||||
})
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
for (const dependency of dependencies) {
|
||||
if (externalize.includes(dependency)) continue
|
||||
if (!packageJsonRoot.dependencies[dependency]) throw new Error(`Dependency ${dependency} not found in package.json`)
|
||||
packageJson.dependencies[dependency] = packageJsonRoot.dependencies[dependency]
|
||||
}
|
||||
fs.writeFileSync(path.resolve(__dirname, '../dist-npm/package.json'), JSON.stringify(packageJson, null, 2))
|
||||
// fs.promises.writeFile('./dist-npm/metafile.json', JSON.stringify(metafile, null, 2))
|
||||
|
||||
await build({
|
||||
entryPoints: ['dist-npm/dist-pre/**/*.js'],
|
||||
outdir: 'dist-npm/dist',
|
||||
// allowOverwrite: true,
|
||||
jsx: 'preserve',
|
||||
bundle: true,
|
||||
target: 'esnext',
|
||||
platform: 'browser',
|
||||
format: 'esm',
|
||||
loader: {
|
||||
'.css': 'copy',
|
||||
'.module.css': 'copy',
|
||||
'.png': 'copy',
|
||||
},
|
||||
minifyWhitespace: false,
|
||||
logOverride: {
|
||||
// 'ignored-bare-import': "info"
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
name: 'all-external',
|
||||
setup (build) {
|
||||
build.onResolve({ filter: /.*/ }, (args) => {
|
||||
// todo use workspace deps
|
||||
if (externalize.some(x => args.path.startsWith(x))) {
|
||||
return undefined // bundle
|
||||
}
|
||||
if (args.path.endsWith('.css') || args.path.endsWith('.png') || args.path.endsWith('.jpg') || args.path.endsWith('.jpeg')) {
|
||||
return undefined // loader action
|
||||
}
|
||||
return {
|
||||
path: args.path,
|
||||
external: true,
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
const paths = await glob('dist-npm/dist-pre/**/*.d.ts')
|
||||
// copy to dist
|
||||
for (const p of paths) {
|
||||
const relative = path.relative('dist-npm/dist-pre', p)
|
||||
const target = path.resolve('dist-npm/dist', relative)
|
||||
fs.copyFileSync(p, target)
|
||||
}
|
||||
// rm dist-pre
|
||||
fs.rmSync('dist-npm/dist-pre', { recursive: true })
|
||||
fs.copyFileSync(path.resolve(__dirname, '../README.NPM.MD'), path.resolve(__dirname, '../dist-npm/README.md'))
|
||||
|
||||
if (version !== '0.0.0-dev') {
|
||||
execSync('npm publish', { cwd: path.resolve(__dirname, '../dist-npm') })
|
||||
}
|
||||
})
|
||||
|
|
@ -2,8 +2,8 @@ import { test, expect } from 'vitest'
|
|||
import mcData from 'minecraft-data'
|
||||
import { formatMessage } from './botUtils'
|
||||
|
||||
globalThis.window ??= {} as any
|
||||
globalThis.window.loadedData ??= mcData('1.20.1')
|
||||
//@ts-expect-error
|
||||
globalThis.loadedData ??= mcData('1.20.1')
|
||||
|
||||
const mapIncludeDefined = (props) => {
|
||||
return (x) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// this should actually be moved to mineflayer / prismarine-viewer
|
||||
|
||||
import { fromFormattedString, TextComponent } from '@xmcl/text-component'
|
||||
import type { IndexedData } from 'minecraft-data'
|
||||
|
||||
export type MessageFormatPart = Pick<TextComponent, 'hoverEvent' | 'clickEvent'> & {
|
||||
text: string
|
||||
|
|
@ -26,8 +27,10 @@ type MessageInput = {
|
|||
json?: any
|
||||
}
|
||||
|
||||
// todo move to sign-renderer, replace with prismarine-chat
|
||||
export const formatMessage = (message: MessageInput) => {
|
||||
const global = globalThis as any
|
||||
|
||||
// todo move to sign-renderer, replace with prismarine-chat, fix mcData issue!
|
||||
export const formatMessage = (message: MessageInput, mcData: IndexedData = global.loadedData) => {
|
||||
let msglist: MessageFormatPart[] = []
|
||||
|
||||
const readMsg = (msg: MessageInput) => {
|
||||
|
|
@ -47,7 +50,7 @@ export const formatMessage = (message: MessageInput) => {
|
|||
...styles
|
||||
})
|
||||
} else if (msg.translate) {
|
||||
const tText = window.loadedData.language[msg.translate] ?? msg.translate
|
||||
const tText = mcData?.language[msg.translate] ?? msg.translate
|
||||
|
||||
if (msg.with) {
|
||||
const splitted = tText.split(/%s|%\d+\$s/g)
|
||||
|
|
@ -114,6 +117,6 @@ const blockToItemRemaps = {
|
|||
}
|
||||
|
||||
export const getItemFromBlock = (block: import('prismarine-block').Block) => {
|
||||
const item = loadedData.itemsByName[blockToItemRemaps[block.name] ?? block.name]
|
||||
const item = global.mcData.itemsByName[blockToItemRemaps[block.name] ?? block.name]
|
||||
return item
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { isGameActive, showModal, gameAdditionalState, activeModalStack, hideCur
|
|||
import { goFullscreen, pointerLock, reloadChunks } from './utils'
|
||||
import { options } from './optionsStorage'
|
||||
import { openPlayerInventory } from './inventoryWindows'
|
||||
import { chatInputValueGlobal } from './react/ChatContainer'
|
||||
import { chatInputValueGlobal } from './react/Chat'
|
||||
import { fsState } from './loadSave'
|
||||
import { showOptionsModal } from './react/SelectOption'
|
||||
import widgets from './react/widgets'
|
||||
|
|
|
|||
|
|
@ -58,7 +58,11 @@ customEvents.on('gameLoaded', () => {
|
|||
}
|
||||
}
|
||||
|
||||
let lastCall = 0
|
||||
bot.on('physicsTick', () => {
|
||||
// throttle, tps: 6
|
||||
if (Date.now() - lastCall < 166) return
|
||||
lastCall = Date.now()
|
||||
for (const [id, { tracking, info }] of Object.entries(bot.tracker.trackingData)) {
|
||||
if (!tracking) continue
|
||||
const e = bot.entities[id]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { showModal } from './globalState'
|
||||
import { chatInputValueGlobal } from './react/ChatContainer'
|
||||
import { chatInputValueGlobal } from './react/Chat'
|
||||
import { showNotification } from './react/NotificationProvider'
|
||||
|
||||
export default () => {
|
||||
|
|
|
|||
44
src/globals.d.ts
vendored
44
src/globals.d.ts
vendored
|
|
@ -30,46 +30,4 @@ declare interface Document {
|
|||
exitPointerLock?(): void
|
||||
}
|
||||
|
||||
declare namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
[elemName: string]: any
|
||||
}
|
||||
}
|
||||
|
||||
declare interface Window extends Record<string, any> {}
|
||||
|
||||
type StringKeys<T extends object> = Extract<keyof T, string>
|
||||
|
||||
|
||||
interface ObjectConstructor {
|
||||
keys<T extends object> (obj: T): Array<StringKeys<T>>
|
||||
entries<T extends object> (obj: T): Array<[StringKeys<T>, T[keyof T]]>
|
||||
// todo review https://stackoverflow.com/questions/57390305/trying-to-get-fromentries-type-right
|
||||
fromEntries<T extends Array<[string, any]>> (obj: T): Record<T[number][0], T[number][1]>
|
||||
assign<T extends Record<string, any>, K extends Record<string, any>> (target: T, source: K): asserts target is T & K
|
||||
}
|
||||
|
||||
declare module '*.module.css' {
|
||||
const css: Record<string, string>
|
||||
export default css
|
||||
}
|
||||
declare module '*.css' {
|
||||
const css: string
|
||||
export default css
|
||||
}
|
||||
declare module '*.json' {
|
||||
const json: any
|
||||
export = json
|
||||
}
|
||||
declare module '*.png' {
|
||||
const png: string
|
||||
export default png
|
||||
}
|
||||
|
||||
interface PromiseConstructor {
|
||||
withResolvers<T> (): {
|
||||
resolve: (value: T) => void;
|
||||
reject: (reason: any) => void;
|
||||
promise: Promise<T>;
|
||||
}
|
||||
}
|
||||
declare interface Window extends Record<string, any> { }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import { fsState } from '../loadSave'
|
|||
import { guessProblem } from '../guessProblem'
|
||||
import AppStatus from './AppStatus'
|
||||
import DiveTransition from './DiveTransition'
|
||||
import { useDidUpdateEffect, useIsModalActive } from './utils'
|
||||
import { useDidUpdateEffect } from './utils'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
import Button from './Button'
|
||||
|
||||
const initialState = {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
--bg-x: calc(-1 * 16px);
|
||||
--bg-y: calc(-1 * 9px);
|
||||
pointer-events: none;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.armor {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export default (
|
|||
{
|
||||
Array.from({ length: 10 }, () => 0)
|
||||
.map(
|
||||
(num, index) => <div
|
||||
(num, index) => <div
|
||||
key={`armor-${index}`}
|
||||
className='armor'></div>
|
||||
)
|
||||
|
|
@ -48,5 +48,3 @@ export default (
|
|||
</div>
|
||||
</SharedHudVars>
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
26
src/react/BarsCommon.tsx
Normal file
26
src/react/BarsCommon.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
const getEffectClass = (effect) => {
|
||||
switch (effect.id) {
|
||||
case 19:
|
||||
return 'poisoned'
|
||||
case 20:
|
||||
return 'withered'
|
||||
case 22:
|
||||
return 'absorption'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export const barEffectAdded = (htmlElement, effect) => {
|
||||
const effectClass = getEffectClass(effect)
|
||||
if (effectClass) {
|
||||
htmlElement.classList.add(effectClass)
|
||||
}
|
||||
}
|
||||
|
||||
export const barEffectEnded = (htmlElement, effect) => {
|
||||
const effectClass = getEffectClass(effect)
|
||||
if (effectClass) {
|
||||
htmlElement.classList.remove(effectClass)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
color: #fff;
|
||||
}
|
||||
.bossbar {
|
||||
background-image: url("textures/1.18.1/gui/bars.png");
|
||||
background-image: url("minecraft-assets/minecraft-assets/data/1.18.1/gui/bars.png");
|
||||
width: 182px;
|
||||
height: 5px;
|
||||
position: relative;
|
||||
|
|
@ -30,5 +30,5 @@
|
|||
left: 0;
|
||||
height: 5px;
|
||||
width: 0;
|
||||
background-image: url("textures/1.18.1/gui/bars.png");
|
||||
background-image: url("minecraft-assets/minecraft-assets/data/1.18.1/gui/bars.png");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
--bg-x: calc(-1 * 16px);
|
||||
--bg-y: calc(-1 * 18px);
|
||||
pointer-events: none;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.breath {
|
||||
|
|
|
|||
18
src/react/Button.stories.tsx
Normal file
18
src/react/Button.stories.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import Button from './Button'
|
||||
|
||||
const meta: Meta<typeof Button> = {
|
||||
component: Button,
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof Button>;
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
label: 'Hello!',
|
||||
icon: 'pixelarticons:lock-open',
|
||||
inScreen: false,
|
||||
},
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import classNames from 'classnames'
|
||||
import { FC, Ref } from 'react'
|
||||
import { loadSound, playSound } from '../basicSounds'
|
||||
import { createContext, FC, Ref, useContext } from 'react'
|
||||
import buttonCss from './button.module.css'
|
||||
import SharedHudVars from './SharedHudVars'
|
||||
|
||||
// testing in storybook from deathscreen
|
||||
|
||||
|
|
@ -13,11 +13,19 @@ interface Props extends React.ComponentProps<'button'> {
|
|||
rootRef?: Ref<HTMLButtonElement>
|
||||
}
|
||||
|
||||
void loadSound('button_click.mp3')
|
||||
const ButtonContext = createContext({
|
||||
onClick () { },
|
||||
})
|
||||
|
||||
export const ButtonProvider: FC<{children, onClick}> = ({ children, onClick }) => {
|
||||
return <ButtonContext.Provider value={{ onClick }}>{children}</ButtonContext.Provider>
|
||||
}
|
||||
|
||||
export default (({ label, icon, children, inScreen, rootRef, type = 'button', ...args }) => {
|
||||
const ctx = useContext(ButtonContext)
|
||||
|
||||
const onClick = (e) => {
|
||||
void playSound('button_click.mp3')
|
||||
ctx.onClick()
|
||||
args.onClick?.(e)
|
||||
}
|
||||
if (inScreen) {
|
||||
|
|
@ -29,9 +37,11 @@ export default (({ label, icon, children, inScreen, rootRef, type = 'button', ..
|
|||
args.style.width = 20
|
||||
}
|
||||
|
||||
return <button ref={rootRef} {...args} className={classNames(buttonCss.button, args.className)} onClick={onClick} type={type}>
|
||||
{icon && <iconify-icon class={buttonCss.icon} icon={icon}></iconify-icon>}
|
||||
{label}
|
||||
{children}
|
||||
</button>
|
||||
return <SharedHudVars>
|
||||
<button ref={rootRef} {...args} className={classNames(buttonCss.button, args.className)} onClick={onClick} type={type}>
|
||||
{icon && <iconify-icon class={buttonCss.icon} icon={icon}></iconify-icon>}
|
||||
{label}
|
||||
{children}
|
||||
</button>
|
||||
</SharedHudVars>
|
||||
}) satisfies FC<Props>
|
||||
|
|
|
|||
10
src/react/ButtonAppProvider.tsx
Normal file
10
src/react/ButtonAppProvider.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { loadSound, playSound } from '../basicSounds'
|
||||
import { ButtonProvider } from './Button'
|
||||
|
||||
void loadSound('button_click.mp3')
|
||||
|
||||
export default ({ children }) => {
|
||||
return <ButtonProvider onClick={() => {
|
||||
void playSound('button_click.mp3')
|
||||
}}>{children}</ButtonProvider>
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'
|
|||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { formatMessage } from '../botUtils'
|
||||
import Chat, { fadeMessage, chatInputValueGlobal } from './ChatContainer'
|
||||
import Chat, { fadeMessage, chatInputValueGlobal } from './Chat'
|
||||
import Button from './Button'
|
||||
|
||||
window.spamMessage = window.spamMessage ?? ''
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ import { useEffect, useMemo, useRef, useState } from 'react'
|
|||
import { isCypress } from '../standaloneUtils'
|
||||
import { MessageFormatPart } from '../botUtils'
|
||||
import { MessagePart } from './MessageFormatted'
|
||||
import './ChatContainer.css'
|
||||
import { isIos } from './utils'
|
||||
import { reactKeyForMessage } from './Scoreboard'
|
||||
import './Chat.css'
|
||||
import { isIos, reactKeyForMessage } from './utils'
|
||||
|
||||
export type Message = {
|
||||
parts: MessageFormatPart[],
|
||||
|
|
@ -4,8 +4,8 @@ import { formatMessage } from '../botUtils'
|
|||
import { getBuiltinCommandsList, tryHandleBuiltinCommand } from '../builtinCommands'
|
||||
import { hideCurrentModal, miscUiState } from '../globalState'
|
||||
import { options } from '../optionsStorage'
|
||||
import ChatContainer, { Message, fadeMessage } from './ChatContainer'
|
||||
import { useIsModalActive } from './utils'
|
||||
import Chat, { Message, fadeMessage } from './Chat'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
import { hideNotification, showNotification } from './NotificationProvider'
|
||||
import { updateLoadedServerData } from './ServersListProvider'
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ export default () => {
|
|||
})
|
||||
}, [])
|
||||
|
||||
return <ChatContainer
|
||||
return <Chat
|
||||
allowSelection={chatSelect}
|
||||
usingTouch={!!usingTouch}
|
||||
opacity={(isChatActive ? chatOpacityOpened : chatOpacity) / 100}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { hideCurrentModal, showModal } from '../globalState'
|
|||
import defaultLocalServerOptions from '../defaultLocalServerOptions'
|
||||
import { mkdirRecursive, uniqueFileNameFromWorldName } from '../browserfs'
|
||||
import CreateWorld, { WorldCustomize, creatingWorldState } from './CreateWorld'
|
||||
import { useIsModalActive } from './utils'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
import { getWorldsPath } from './SingleplayerProvider'
|
||||
|
||||
export default () => {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { MessageFormatPart, formatMessage } from '../botUtils'
|
|||
import { showModal, hideModal } from '../globalState'
|
||||
import { options } from '../optionsStorage'
|
||||
import DeathScreen from './DeathScreen'
|
||||
import { useIsModalActive } from './utils'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
|
||||
const dieReasonProxy = proxy({ value: null as MessageFormatPart[] | null })
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { Transition } from 'react-transition-group'
|
||||
import styles from './diveAnimation.module.css'
|
||||
import styles from './DiveTransition.module.css'
|
||||
|
||||
// dive animation from framework7
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import Button from './Button'
|
||||
import { useUsingTouch } from './utils'
|
||||
import { useUsingTouch } from './utilsApp'
|
||||
|
||||
export default () => {
|
||||
const [fullScreen, setFullScreen] = useState(false)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
--bg-x: calc(-1 * (16px + 9px * var(--lightened)));
|
||||
--bg-y: calc(-1 * 27px);
|
||||
pointer-events: none;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.foodbar.poisoned {
|
||||
|
|
|
|||
19
src/react/FoodBar.stories.tsx
Normal file
19
src/react/FoodBar.stories.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import FoodBar from './FoodBar'
|
||||
|
||||
const meta: Meta<typeof FoodBar> = {
|
||||
component: FoodBar
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof FoodBar>;
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
gameMode: 'survival',
|
||||
food: 10,
|
||||
effectToAdd: 19,
|
||||
effectToRemove: 20,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
import { useRef, useState, useEffect } from 'react'
|
||||
import SharedHudVars from './SharedHudVars'
|
||||
import './FoodBar.css'
|
||||
import { barEffectAdded, barEffectEnded } from './BarsCommon'
|
||||
|
||||
|
||||
export type FoodBarProps = {
|
||||
gameMode: string,
|
||||
gameMode?: string,
|
||||
food: number,
|
||||
effectToAdd: number | null,
|
||||
effectToRemove: number | null,
|
||||
effectAdded: (htmlElement: HTMLDivElement | null, effect: number | null) => void,
|
||||
effectEnded: (htmlElement: HTMLDivElement | null, effect: number | null) => void,
|
||||
effectToAdd?: number | null,
|
||||
effectToRemove?: number | null,
|
||||
resetEffects?: () => void,
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export default (
|
||||
|
|
@ -18,8 +19,8 @@ export default (
|
|||
food,
|
||||
effectToAdd,
|
||||
effectToRemove,
|
||||
effectAdded,
|
||||
effectEnded
|
||||
resetEffects,
|
||||
style
|
||||
}: FoodBarProps) => {
|
||||
const foodRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
|
|
@ -54,15 +55,17 @@ export default (
|
|||
}, [food])
|
||||
|
||||
useEffect(() => {
|
||||
effectAdded(foodRef.current, effectToAdd)
|
||||
}, [effectToAdd])
|
||||
if (effectToAdd) {
|
||||
barEffectAdded(foodRef.current, effectToAdd)
|
||||
}
|
||||
if (effectToRemove) {
|
||||
barEffectEnded(foodRef.current, effectToRemove)
|
||||
}
|
||||
resetEffects?.()
|
||||
}, [effectToAdd, effectToRemove])
|
||||
|
||||
useEffect(() => {
|
||||
effectEnded(foodRef.current, effectToRemove)
|
||||
}, [effectToRemove])
|
||||
|
||||
return <SharedHudVars>
|
||||
<div ref={foodRef} className='foodbar' >
|
||||
return <SharedHudVars>
|
||||
<div ref={foodRef} className='foodbar' style={style}>
|
||||
{
|
||||
Array.from({ length: 10 }, () => 0)
|
||||
.map(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Screen from './Screen'
|
||||
import { useIsWidgetActive } from './utils'
|
||||
import { useIsWidgetActive } from './utilsApp'
|
||||
|
||||
export default ({ name, title, children }) => {
|
||||
const isWidgetActive = useIsWidgetActive(name)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
--bg-x: calc(-1 * (16px + 9px * var(--lightened)));
|
||||
--bg-y: calc(-1 * var(--hardcore) * 45px);
|
||||
pointer-events: none;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.health.creative {
|
||||
|
|
|
|||
|
|
@ -9,19 +9,6 @@ const meta: Meta<typeof HealthBar> = {
|
|||
export default meta
|
||||
type Story = StoryObj<typeof HealthBar>;
|
||||
|
||||
const getEffectClass = (effect) => {
|
||||
switch (effect.id) {
|
||||
case 19:
|
||||
return 'poisoned'
|
||||
case 20:
|
||||
return 'withered'
|
||||
case 22:
|
||||
return 'absorption'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
gameMode: 'survival',
|
||||
|
|
@ -30,16 +17,5 @@ export const Primary: Story = {
|
|||
healthValue: 10,
|
||||
effectToAdd: 19,
|
||||
effectToRemove: 20,
|
||||
effectAdded (htmlElement, effect) {
|
||||
const effectClass = getEffectClass(effect)
|
||||
if (!effectClass) return
|
||||
if (htmlElement) htmlElement.classList.add(effectClass)
|
||||
},
|
||||
effectEnded (htmlElement, effect) {
|
||||
const effectClass = getEffectClass(effect)
|
||||
if (!effectClass) return
|
||||
if (htmlElement) htmlElement.classList.remove(effectClass)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,36 @@
|
|||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useRef, useEffect } from 'react'
|
||||
import SharedHudVars from './SharedHudVars'
|
||||
import './HealthBar.css'
|
||||
import { barEffectAdded, barEffectEnded } from './BarsCommon'
|
||||
|
||||
|
||||
export type HealthBarProps = {
|
||||
gameMode: string,
|
||||
gameMode?: string,
|
||||
isHardcore: boolean,
|
||||
damaged: boolean,
|
||||
healthValue: number,
|
||||
effectToAdd: number | null,
|
||||
effectToRemove: number | null,
|
||||
effectAdded: (htmlElement: HTMLDivElement | null, effect: number | null) => void,
|
||||
effectEnded: (htmlElement: HTMLDivElement | null, effect: number | null) => void,
|
||||
effectToAdd?: number | null,
|
||||
effectToRemove?: number | null,
|
||||
resetEffects?: () => void
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export default (
|
||||
{
|
||||
gameMode,
|
||||
isHardcore,
|
||||
damaged,
|
||||
healthValue,
|
||||
gameMode,
|
||||
isHardcore,
|
||||
damaged,
|
||||
healthValue,
|
||||
effectToAdd,
|
||||
effectToRemove,
|
||||
effectAdded,
|
||||
effectEnded
|
||||
resetEffects,
|
||||
style
|
||||
}: HealthBarProps) => {
|
||||
const healthRef = useRef<HTMLDivElement | null>(null)
|
||||
const [className, setClassName] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (healthRef.current) {
|
||||
healthRef.current.classList.toggle('creative', gameMode === 'creative' || gameMode === 'spectator')
|
||||
// if (gameMode === 'creative' || gameMode === 'spectator') {
|
||||
// healthRef.current.classList.add('creative')
|
||||
// } else {
|
||||
// healthRef.current.classList.remove('creative')
|
||||
// }
|
||||
}
|
||||
}, [gameMode])
|
||||
|
||||
|
|
@ -89,24 +84,24 @@ export default (
|
|||
}, [healthValue])
|
||||
|
||||
useEffect(() => {
|
||||
effectAdded(healthRef.current, effectToAdd)
|
||||
}, [effectToAdd])
|
||||
|
||||
useEffect(() => {
|
||||
effectEnded(healthRef.current, effectToRemove)
|
||||
}, [effectToRemove])
|
||||
if (effectToAdd) {
|
||||
barEffectAdded(healthRef.current, effectToAdd)
|
||||
}
|
||||
if (effectToRemove) {
|
||||
barEffectEnded(healthRef.current, effectToRemove)
|
||||
}
|
||||
resetEffects?.()
|
||||
}, [effectToAdd, effectToRemove])
|
||||
|
||||
return <SharedHudVars>
|
||||
<div ref={healthRef} className='health' >
|
||||
<div ref={healthRef} className='health' style={style}>
|
||||
{
|
||||
Array.from({ length: 10 }, () => 0)
|
||||
.map(
|
||||
(num, index) => <div
|
||||
(num, index) => <div
|
||||
key={`heart-${index}`}
|
||||
className='heart'></div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</SharedHudVars>}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -32,22 +32,6 @@ export default () => {
|
|||
}
|
||||
}
|
||||
|
||||
const effectAdded = (htmlElement, effect) => {
|
||||
const effectClass = getEffectClass(effect)
|
||||
if (effectClass) {
|
||||
htmlElement.classList.add(effectClass)
|
||||
}
|
||||
setEffectToAdd(null)
|
||||
}
|
||||
|
||||
const effectEnded = (htmlElement, effect) => {
|
||||
const effectClass = getEffectClass(effect)
|
||||
if (effectClass) {
|
||||
htmlElement.classList.remove(effectClass)
|
||||
}
|
||||
setEffectToRemove(null)
|
||||
}
|
||||
|
||||
const onDamage = () => {
|
||||
setDamaged(prev => true)
|
||||
if (hurtTimeout.current) clearTimeout(hurtTimeout.current)
|
||||
|
|
@ -114,8 +98,10 @@ export default () => {
|
|||
healthValue={healthValue}
|
||||
effectToAdd={effectToAdd}
|
||||
effectToRemove={effectToRemove}
|
||||
effectAdded={effectAdded}
|
||||
effectEnded={effectEnded}
|
||||
resetEffects={() => {
|
||||
setEffectToAdd(null)
|
||||
setEffectToRemove(null)
|
||||
}}
|
||||
/>
|
||||
<ArmorBar
|
||||
armorValue={armorValue}
|
||||
|
|
@ -126,8 +112,10 @@ export default () => {
|
|||
food={food}
|
||||
effectToAdd={effectToAdd}
|
||||
effectToRemove={effectToRemove}
|
||||
effectAdded={effectAdded}
|
||||
effectEnded={effectEnded}
|
||||
resetEffects={() => {
|
||||
setEffectToAdd(null)
|
||||
setEffectToRemove(null)
|
||||
}}
|
||||
/>
|
||||
<BreathBar
|
||||
oxygen={gameMode !== 'survival' && gameMode !== 'adventure' ? 0 : oxygen}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useRef } from 'react'
|
||||
import { isMobile } from 'prismarine-viewer/viewer/lib/simpleUtils'
|
||||
import styles from './input.module.css'
|
||||
import { useUsingTouch } from './utils'
|
||||
|
||||
interface Props extends React.ComponentProps<'input'> {
|
||||
rootStyles?: React.CSSProperties
|
||||
|
|
@ -10,11 +10,10 @@ interface Props extends React.ComponentProps<'input'> {
|
|||
|
||||
export default ({ autoFocus, rootStyles, inputRef, ...inputProps }: Props) => {
|
||||
const ref = useRef<HTMLInputElement>(null!)
|
||||
const isTouch = useUsingTouch()
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef) (inputRef as any).current = ref.current
|
||||
if (!autoFocus || isTouch) return // Don't make screen keyboard popup on mobile
|
||||
if (!autoFocus || isMobile()) return // Don't make screen keyboard popup on mobile
|
||||
ref.current.focus()
|
||||
}, [])
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { noCase } from 'change-case'
|
|||
import mojangson from 'mojangson'
|
||||
import { openURL } from 'prismarine-viewer/viewer/lib/simpleUtils'
|
||||
import { MessageFormatPart } from '../botUtils'
|
||||
import { chatInputValueGlobal } from './ChatContainer'
|
||||
import { chatInputValueGlobal } from './Chat'
|
||||
import './MessageFormatted.css'
|
||||
|
||||
const hoverItemToText = (hoverEvent: MessageFormatPart['hoverEvent']) => {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { disconnect } from '../flyingSquidUtils'
|
|||
import { pointerLock, setLoadingScreenStatus } from '../utils'
|
||||
import { closeWan, openToWanAndCopyJoinLink, getJoinLink } from '../localServerMultiplayer'
|
||||
import { copyFilesAsyncWithProgress, mkdirRecursive, uniqueFileNameFromWorldName } from '../browserfs'
|
||||
import { useIsModalActive } from './utils'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
import { showOptionsModal } from './SelectOption'
|
||||
import Button from './Button'
|
||||
import Screen from './Screen'
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ import { CSSProperties } from 'react'
|
|||
// names: https://pixelarticons.com/free/
|
||||
export default ({ iconName, width = undefined as undefined | number, styles = {} as CSSProperties, className = undefined }) => {
|
||||
if (width !== undefined) styles = { width, height: width, ...styles }
|
||||
return <iconify-icon icon={`pixelarticons:${iconName}`} style={styles} className={className} />
|
||||
return <iconify-icon icon={`pixelarticons:${iconName}`} style={styles} class={className} />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
position: absolute;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
top: 9px;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
padding: 1px;
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -11,8 +11,14 @@ type Story = StoryObj<typeof PlayerListOverlay>;
|
|||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
playersLists: [],
|
||||
clientId: '',
|
||||
playersLists: [
|
||||
[
|
||||
{ username: 'Player 1', ping: 10, uuid: '1' },
|
||||
{ username: 'Player 2', ping: 20, uuid: '2' },
|
||||
{ username: 'Player 3', ping: 30, uuid: '3' },
|
||||
]
|
||||
],
|
||||
clientId: '2',
|
||||
tablistHeader: 'Header',
|
||||
tablistFooter: 'Footer',
|
||||
serverIP: '95.163.228.101',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import MessageFormattedString from './MessageFormattedString'
|
|||
import './PlayerListOverlay.css'
|
||||
|
||||
|
||||
type PlayersLists = Array<Array<import('mineflayer').Player>>
|
||||
type PlayersLists = Array<Array<Pick<import('mineflayer').Player, 'uuid'|'username'|'ping'>>>
|
||||
|
||||
type PlayerListOverlayProps = {
|
||||
playersLists: PlayersLists,
|
||||
|
|
@ -10,11 +10,12 @@ type PlayerListOverlayProps = {
|
|||
tablistHeader: string | Record<string, any> | null,
|
||||
tablistFooter: string | Record<string, any> | null,
|
||||
serverIP: string
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export default ({ playersLists, clientId, tablistHeader, tablistFooter, serverIP }: PlayerListOverlayProps) => {
|
||||
export default ({ playersLists, clientId, tablistHeader, tablistFooter, serverIP, style }: PlayerListOverlayProps) => {
|
||||
|
||||
return <div className="playerlist-container" id="playerlist-container" >
|
||||
return <div className="playerlist-container" id="playerlist-container" style={style}>
|
||||
<span className="playerlist-title">Server IP: {serverIP}</span>
|
||||
<div className='playerlist-header'>
|
||||
<MessageFormattedString message={tablistHeader} />
|
||||
|
|
@ -23,7 +24,7 @@ export default ({ playersLists, clientId, tablistHeader, tablistFooter, serverIP
|
|||
{playersLists.map((list, index) => (
|
||||
<div key={index} className="player-list">
|
||||
{list.map(player => (
|
||||
<div key={player.uuid} className={`playerlist-entry${clientId === player.uuid ? ' active-player' : ''}`} id={`plist-player-${player.uuid}`}>
|
||||
<div key={player.uuid ?? player.username} className={`playerlist-entry${clientId === player.uuid ? ' active-player' : ''}`} id={`plist-player-${player.uuid}`}>
|
||||
<MessageFormattedString message={player.username} />
|
||||
<div className="playerlist-ping">
|
||||
<p className="playerlist-ping-value">{player.ping}</p>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import './Scoreboard.css'
|
||||
import MessageFormattedString from './MessageFormattedString'
|
||||
import { reactKeyForMessage } from './utils'
|
||||
|
||||
|
||||
export type ScoreboardItems = Array<{name: string, value: number, displayName?: any}>
|
||||
|
|
@ -8,17 +9,14 @@ type ScoreboardProps = {
|
|||
title: string,
|
||||
items: ScoreboardItems,
|
||||
open: boolean
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export const reactKeyForMessage = (message) => {
|
||||
return typeof message === 'string' ? message : JSON.stringify(message)
|
||||
}
|
||||
|
||||
export default function Scoreboard ({ title, items, open }: ScoreboardProps) {
|
||||
export default function Scoreboard ({ title, items, open, style }: ScoreboardProps) {
|
||||
|
||||
if (!open) return null
|
||||
return (
|
||||
<div className='scoreboard-container'>
|
||||
<div className='scoreboard-container' style={style}>
|
||||
<div className='scoreboard-title'>
|
||||
<MessageFormattedString message={title} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { proxy, useSnapshot } from 'valtio'
|
||||
import { hideCurrentModal, showModal } from '../globalState'
|
||||
import Screen from './Screen'
|
||||
import { useIsModalActive } from './utils'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
import Button from './Button'
|
||||
|
||||
const state = proxy({
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import { ConnectOptions } from '../connect'
|
|||
import { hideCurrentModal, miscUiState, showModal } from '../globalState'
|
||||
import ServersList from './ServersList'
|
||||
import AddServer from './AddServer'
|
||||
import { useDidUpdateEffect, useIsModalActive } from './utils'
|
||||
import { useDidUpdateEffect } from './utils'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
|
||||
interface StoreServerItem {
|
||||
ip: string,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { CSSProperties, useEffect } from 'react'
|
||||
import icons from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/icons.png'
|
||||
import widgets from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/widgets.png'
|
||||
|
||||
export default ({ children }) => {
|
||||
useEffect(() => {
|
||||
|
|
@ -8,7 +9,9 @@ export default ({ children }) => {
|
|||
// 2. Easier application to globally override icons with custom image (eg from resourcepacks)
|
||||
const css = /* css */`
|
||||
:root {
|
||||
--widgets-gui-atlas: url(${widgets});
|
||||
--gui-icons: url(${icons}), url(${icons});
|
||||
--safe-area-inset-bottom: calc(env(safe-area-inset-bottom) / 2);
|
||||
}
|
||||
`
|
||||
const style = document.createElement('style')
|
||||
|
|
@ -17,11 +20,5 @@ export default ({ children }) => {
|
|||
document.head.appendChild(style)
|
||||
}, [])
|
||||
|
||||
const customVars = {
|
||||
'--safe-area-inset-bottom': 'calc(env(safe-area-inset-bottom) / 2)'
|
||||
} as CSSProperties
|
||||
|
||||
return <div
|
||||
style={customVars}
|
||||
>{children}</div>
|
||||
return children
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useMemo, useEffect, useState, useRef } from 'react'
|
|||
import { showModal, hideModal } from '../globalState'
|
||||
import { setDoPreventDefault } from '../controls'
|
||||
import { options } from '../optionsStorage'
|
||||
import { useIsModalActive } from './utils'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
import SignEditor, { ResultType } from './SignEditor'
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { haveDirectoryPicker, setLoadingScreenStatus } from '../utils'
|
|||
import { exportWorld } from '../builtinCommands'
|
||||
import { googleProviderState, useGoogleLogIn, GoogleDriveProvider, isGoogleDriveAvailable, APP_ID } from '../googledrive'
|
||||
import Singleplayer, { WorldProps } from './Singleplayer'
|
||||
import { useIsModalActive } from './utils'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
import { showOptionsModal } from './SelectOption'
|
||||
import Input from './Input'
|
||||
import GoogleButton from './GoogleButton'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Slider.tsx
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import styles from './slider.module.css'
|
||||
import SharedHudVars from './SharedHudVars'
|
||||
|
||||
interface Props extends React.ComponentProps<'div'> {
|
||||
label: string;
|
||||
|
|
@ -47,36 +48,38 @@ const Slider: React.FC<Props> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={styles['slider-container']} style={{ width }} {...divProps}>
|
||||
<input
|
||||
type="range"
|
||||
className={styles.slider}
|
||||
min={min}
|
||||
max={max}
|
||||
value={value}
|
||||
disabled={!!disabledReason}
|
||||
onChange={(e) => {
|
||||
const newValue = Number(e.target.value)
|
||||
setValue(newValue)
|
||||
fireValueUpdate(false, newValue)
|
||||
}}
|
||||
// todo improve correct handling of drag end
|
||||
onLostPointerCapture={() => {
|
||||
fireValueUpdate(true)
|
||||
}}
|
||||
onPointerUp={() => {
|
||||
fireValueUpdate(true)
|
||||
}}
|
||||
onKeyUp={() => {
|
||||
fireValueUpdate(true)
|
||||
}}
|
||||
/>
|
||||
<div className={styles.disabled} title={disabledReason}></div>
|
||||
<div className={styles['slider-thumb']} style={{ left: `calc((100% * ${ratio}) - (8px * ${ratio}))` }}></div>
|
||||
<label className={styles.label}>
|
||||
{label}: {valueDisplay ?? value} {unit}
|
||||
</label>
|
||||
</div>
|
||||
<SharedHudVars>
|
||||
<div className={styles['slider-container']} style={{ width }} {...divProps}>
|
||||
<input
|
||||
type="range"
|
||||
className={styles.slider}
|
||||
min={min}
|
||||
max={max}
|
||||
value={value}
|
||||
disabled={!!disabledReason}
|
||||
onChange={(e) => {
|
||||
const newValue = Number(e.target.value)
|
||||
setValue(newValue)
|
||||
fireValueUpdate(false, newValue)
|
||||
}}
|
||||
// todo improve correct handling of drag end
|
||||
onLostPointerCapture={() => {
|
||||
fireValueUpdate(true)
|
||||
}}
|
||||
onPointerUp={() => {
|
||||
fireValueUpdate(true)
|
||||
}}
|
||||
onKeyUp={() => {
|
||||
fireValueUpdate(true)
|
||||
}}
|
||||
/>
|
||||
<div className={styles.disabled} title={disabledReason}></div>
|
||||
<div className={styles['slider-thumb']} style={{ left: `calc((100% * ${ratio}) - (8px * ${ratio}))` }}></div>
|
||||
<label className={styles.label}>
|
||||
{label}: {valueDisplay ?? value} {unit}
|
||||
</label>
|
||||
</div>
|
||||
</SharedHudVars>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { lastPlayedSounds } from '../soundSystem'
|
|||
import { options } from '../optionsStorage'
|
||||
import Button from './Button'
|
||||
import Screen from './Screen'
|
||||
import { useIsModalActive } from './utils'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
|
||||
const SoundRow = ({ sound, children }) => {
|
||||
const { mutedSounds } = useSnapshot(options)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useSnapshot } from 'valtio'
|
|||
import { activeModalStack, hideModal } from '../globalState'
|
||||
import { options } from '../optionsStorage'
|
||||
import TouchAreasControls from './TouchAreasControls'
|
||||
import { useIsModalActive, useUsingTouch } from './utils'
|
||||
import { useIsModalActive, useUsingTouch } from './utilsApp'
|
||||
|
||||
export default () => {
|
||||
const usingTouch = useUsingTouch()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useSnapshot } from 'valtio'
|
|||
import { contro } from '../controls'
|
||||
import { miscUiState, activeModalStack } from '../globalState'
|
||||
import { watchValue, options } from '../optionsStorage'
|
||||
import { useUsingTouch } from './utils'
|
||||
import { useUsingTouch } from './utilsApp'
|
||||
|
||||
// todo
|
||||
useInterfaceState.setState({
|
||||
|
|
|
|||
45
src/react/globals.d.ts
vendored
Normal file
45
src/react/globals.d.ts
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
type StringKeys<T extends object> = Extract<keyof T, string>
|
||||
|
||||
|
||||
interface ObjectConstructor {
|
||||
keys<T extends object> (obj: T): Array<StringKeys<T>>
|
||||
entries<T extends object> (obj: T): Array<[StringKeys<T>, T[keyof T]]>
|
||||
// todo review https://stackoverflow.com/questions/57390305/trying-to-get-fromentries-type-right
|
||||
fromEntries<T extends Array<[string, any]>> (obj: T): Record<T[number][0], T[number][1]>
|
||||
assign<T extends Record<string, any>, K extends Record<string, any>> (target: T, source: K): asserts target is T & K
|
||||
}
|
||||
|
||||
declare module '*.module.css' {
|
||||
const css: Record<string, string>
|
||||
export default css
|
||||
}
|
||||
declare module '*.css' {
|
||||
const css: string
|
||||
export default css
|
||||
}
|
||||
declare module '*.json' {
|
||||
const json: any
|
||||
export = json
|
||||
}
|
||||
declare module '*.png' {
|
||||
const png: string
|
||||
export default png
|
||||
}
|
||||
|
||||
interface PromiseConstructor {
|
||||
withResolvers<T> (): {
|
||||
resolve: (value: T) => void;
|
||||
reject: (reason: any) => void;
|
||||
promise: Promise<T>;
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
'iconify-icon': {
|
||||
icon: string
|
||||
style?: React.CSSProperties
|
||||
class?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/react/npmReactEntrypoint.ts
Normal file
1
src/react/npmReactEntrypoint.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
// export * from './npmReactComponents'
|
||||
|
|
@ -1,16 +1,5 @@
|
|||
import { useSnapshot } from 'valtio'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { UAParser } from 'ua-parser-js'
|
||||
import { activeModalStack, miscUiState } from '../globalState'
|
||||
|
||||
export const useIsModalActive = (modal: string, useIncludes = false) => {
|
||||
const allStack = useSnapshot(activeModalStack)
|
||||
return useIncludes ? allStack.some(x => x.reactType === modal) : allStack.at(-1)?.reactType === modal
|
||||
}
|
||||
|
||||
export const useIsWidgetActive = (name: string) => {
|
||||
return useIsModalActive(`widget-${name}`)
|
||||
}
|
||||
|
||||
export function useDidUpdateEffect (fn, inputs) {
|
||||
const isMountingRef = useRef(false)
|
||||
|
|
@ -28,10 +17,10 @@ export function useDidUpdateEffect (fn, inputs) {
|
|||
}, inputs)
|
||||
}
|
||||
|
||||
export const useUsingTouch = () => {
|
||||
return useSnapshot(miscUiState).currentTouch
|
||||
}
|
||||
|
||||
export const ua = new UAParser(navigator.userAgent)
|
||||
|
||||
export const isIos = ua.getOS().name === 'iOS'
|
||||
|
||||
export const reactKeyForMessage = (message) => {
|
||||
return typeof message === 'string' ? message : JSON.stringify(message)
|
||||
}
|
||||
|
|
|
|||
15
src/react/utilsApp.ts
Normal file
15
src/react/utilsApp.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { useSnapshot } from 'valtio'
|
||||
import { activeModalStack, miscUiState } from '../globalState'
|
||||
|
||||
|
||||
export const useUsingTouch = () => {
|
||||
return useSnapshot(miscUiState).currentTouch
|
||||
}
|
||||
export const useIsModalActive = (modal: string, useIncludes = false) => {
|
||||
const allStack = useSnapshot(activeModalStack)
|
||||
return useIncludes ? allStack.some(x => x.reactType === modal) : allStack.at(-1)?.reactType === modal
|
||||
}
|
||||
|
||||
export const useIsWidgetActive = (name: string) => {
|
||||
return useIsModalActive(`widget-${name}`)
|
||||
}
|
||||
|
|
@ -27,12 +27,13 @@ import PauseScreen from './react/PauseScreen'
|
|||
import SoundMuffler from './react/SoundMuffler'
|
||||
import TouchControls from './react/TouchControls'
|
||||
import widgets from './react/widgets'
|
||||
import { useIsWidgetActive } from './react/utils'
|
||||
import { useIsWidgetActive } from './react/utilsApp'
|
||||
import GlobalSearchInput from './GlobalSearchInput'
|
||||
import TouchAreasControlsProvider from './react/TouchAreasControlsProvider'
|
||||
import NotificationProvider, { showNotification } from './react/NotificationProvider'
|
||||
import HotbarRenderApp from './react/HotbarRenderApp'
|
||||
import Crosshair from './react/Crosshair'
|
||||
import ButtonAppProvider from './react/ButtonAppProvider'
|
||||
import ServersListProvider from './react/ServersListProvider'
|
||||
|
||||
const RobustPortal = ({ children, to }) => {
|
||||
|
|
@ -112,7 +113,7 @@ const InGameUi = () => {
|
|||
<DisplayQr />
|
||||
</PerComponentErrorBoundary>
|
||||
<RobustPortal to={document.body}>
|
||||
{/* becaues of z-index */}
|
||||
{/* because of z-index */}
|
||||
<TouchControls />
|
||||
<GlobalSearchInput />
|
||||
</RobustPortal>
|
||||
|
|
@ -132,21 +133,23 @@ const WidgetDisplay = ({ name, Component }) => {
|
|||
|
||||
const App = () => {
|
||||
return <div>
|
||||
<EnterFullscreenButton />
|
||||
<InGameUi />
|
||||
<RobustPortal to={document.querySelector('#ui-root')}>
|
||||
<AllWidgets />
|
||||
<SingleplayerProvider />
|
||||
<CreateWorldProvider />
|
||||
<AppStatusProvider />
|
||||
<SelectOption />
|
||||
<ServersListProvider />
|
||||
<OptionsRenderApp />
|
||||
<MainMenuRenderApp />
|
||||
<NotificationProvider />
|
||||
{/* <GameHud>
|
||||
</GameHud> */}
|
||||
</RobustPortal>
|
||||
<ButtonAppProvider>
|
||||
<EnterFullscreenButton />
|
||||
<InGameUi />
|
||||
<RobustPortal to={document.querySelector('#ui-root')}>
|
||||
<AllWidgets />
|
||||
<SingleplayerProvider />
|
||||
<CreateWorldProvider />
|
||||
<AppStatusProvider />
|
||||
<SelectOption />
|
||||
<ServersListProvider />
|
||||
<OptionsRenderApp />
|
||||
<MainMenuRenderApp />
|
||||
<NotificationProvider />
|
||||
{/* <GameHud>
|
||||
</GameHud> */}
|
||||
</RobustPortal>
|
||||
</ButtonAppProvider>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ html {
|
|||
}
|
||||
|
||||
body {
|
||||
--widgets-gui-atlas: url('minecraft-assets/minecraft-assets/data/1.17.1/gui/widgets.png');
|
||||
--title-gui: url('minecraft-assets/minecraft-assets/data/1.17.1/gui/title/minecraft.png');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,8 @@ class WorldInteraction {
|
|||
|
||||
if (entity && e.button === 2) {
|
||||
bot.attack(entity)
|
||||
} else {
|
||||
// bot
|
||||
}
|
||||
})
|
||||
document.addEventListener('blur', (e) => {
|
||||
|
|
|
|||
12
tsconfig.npm.json
Normal file
12
tsconfig.npm.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist-npm/dist-pre",
|
||||
"noEmit": false,
|
||||
"declaration": true
|
||||
},
|
||||
"include": [
|
||||
"src/react/npmReactComponents.ts",
|
||||
"src/react/globals.d.ts"
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue