feat: new scalable options GUI. Refactored options & main menu to React
chore: added storybook! fixed too big ts language service memory consumption (json mc-data)
This commit is contained in:
parent
803b5d2b98
commit
9351732d09
45 changed files with 1171 additions and 788 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
- run: pnpm lint
|
||||
- run: pnpm check-build
|
||||
- run: nohup pnpm prod-start &
|
||||
- run: nohup node cypress/minecraft-server.mjs &
|
||||
- run: nohup pnpm test-mc-server &
|
||||
- uses: cypress-io/github-action@v5
|
||||
with:
|
||||
install: false
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ it('Loads & renders singleplayer', () => {
|
|||
},
|
||||
renderDistance: 2
|
||||
})
|
||||
cy.get('#title-screen').find('[data-test-id="singleplayer-button"]', { includeShadowDom: true }).click()
|
||||
cy.get('[data-test-id="singleplayer-button"]', { includeShadowDom: true }).click()
|
||||
testWorldLoad()
|
||||
})
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ it('Joins to server', {
|
|||
window.localStorage.version = ''
|
||||
visit()
|
||||
// todo replace with data-test
|
||||
cy.get('#title-screen').find('[data-test-id="connect-screen-button"]', { includeShadowDom: true }).click()
|
||||
cy.get('[data-test-id="connect-screen-button"]', { includeShadowDom: true }).click()
|
||||
cy.get('input#serverip', { includeShadowDom: true }).clear().focus().type('localhost')
|
||||
cy.get('[data-test-id="connect-to-server"]', { includeShadowDom: true }).click()
|
||||
testWorldLoad()
|
||||
|
|
@ -68,7 +68,7 @@ it('Joins to server', {
|
|||
|
||||
it('Loads & renders zip world', () => {
|
||||
cleanVisit()
|
||||
cy.get('#title-screen').find('[data-test-id="select-file-folder"]', { includeShadowDom: true }).click({ shiftKey: true })
|
||||
cy.get('[data-test-id="select-file-folder"]', { includeShadowDom: true }).click({ shiftKey: true })
|
||||
cy.get('input[type="file"]').selectFile('cypress/superflat.zip', { force: true })
|
||||
testWorldLoad()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -44,9 +44,6 @@
|
|||
<pmui-loading-error-screen id="loading-error-screen" style="display: none;"></pmui-loading-error-screen>
|
||||
<pmui-playscreen id="play-screen" style="display: none;"></pmui-playscreen>
|
||||
<pmui-keybindsscreen id="keybinds-screen" style="display: none;"></pmui-keybindsscreen>
|
||||
<pmui-optionsscreen id="options-screen" style="display: none;"></pmui-optionsscreen>
|
||||
<pmui-advanced-optionsscreen style="display: none;"></pmui-advanced-optionsscreen>
|
||||
<pmui-titlescreen id="title-screen" style="display: none;"></pmui-titlescreen>
|
||||
<pmui-notification></pmui-notification>
|
||||
<context-menu id="context-menu"></context-menu>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
"react-dom": "^18.2.0",
|
||||
"stats-gl": "^1.0.5",
|
||||
"stats.js": "^0.17.0",
|
||||
"title-case": "3.x",
|
||||
"valtio": "^1.11.1",
|
||||
"workbox-build": "^7.0.0"
|
||||
},
|
||||
|
|
@ -110,7 +111,8 @@
|
|||
"prismarine-world": "github:zardoy/prismarine-world#next-era",
|
||||
"minecraft-data": "3.45.0",
|
||||
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
|
||||
"minecraft-protocol": "github:zardoy/minecraft-protocol#custom-client-extra"
|
||||
"minecraft-protocol": "github:zardoy/minecraft-protocol#custom-client-extra",
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
197
pnpm-lock.yaml
generated
197
pnpm-lock.yaml
generated
|
|
@ -10,6 +10,7 @@ overrides:
|
|||
minecraft-data: 3.45.0
|
||||
prismarine-provider-anvil: github:zardoy/prismarine-provider-anvil#everything
|
||||
minecraft-protocol: github:zardoy/minecraft-protocol#custom-client-extra
|
||||
react: ^18.2.0
|
||||
|
||||
importers:
|
||||
|
||||
|
|
@ -108,6 +109,9 @@ importers:
|
|||
stats.js:
|
||||
specifier: ^0.17.0
|
||||
version: 0.17.0
|
||||
title-case:
|
||||
specifier: 3.x
|
||||
version: 3.0.3
|
||||
valtio:
|
||||
specifier: ^1.11.1
|
||||
version: 1.11.2(@types/react@18.2.20)(react@18.2.0)
|
||||
|
|
@ -1748,7 +1752,7 @@ packages:
|
|||
/@dimaka/interface@0.0.3-alpha.0(@babel/core@7.22.11)(@popperjs/core@2.11.8)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-BzRUaLk+jhC1uvcix4SXRGhw7k39pldlvwtRT0NGFsuQt1uMV9vOhGMzm3rLorVXQ7vAmqcZk3Hy8oYlXKQoDw==}
|
||||
peerDependencies:
|
||||
react: ^17.0.2
|
||||
react: ^18.2.0
|
||||
dependencies:
|
||||
'@emotion/css': 11.5.0(@babel/core@7.22.11)
|
||||
'@juggle/resize-observer': 3.3.1
|
||||
|
|
@ -1844,7 +1848,7 @@ packages:
|
|||
/@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0):
|
||||
resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react: ^18.2.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
|
@ -2293,7 +2297,7 @@ packages:
|
|||
/@floating-ui/react-dom@2.0.2(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react: ^18.2.0
|
||||
react-dom: '>=16.8.0'
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.5.3
|
||||
|
|
@ -2888,7 +2892,7 @@ packages:
|
|||
/@mdx-js/react@2.3.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==}
|
||||
peerDependencies:
|
||||
react: '>=16'
|
||||
react: ^18.2.0
|
||||
dependencies:
|
||||
'@types/mdx': 2.0.8
|
||||
'@types/react': 18.2.20
|
||||
|
|
@ -2963,7 +2967,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -2984,7 +2988,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -3007,7 +3011,7 @@ packages:
|
|||
resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -3021,7 +3025,7 @@ packages:
|
|||
resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -3035,7 +3039,7 @@ packages:
|
|||
resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -3050,7 +3054,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -3074,7 +3078,7 @@ packages:
|
|||
resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -3089,7 +3093,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -3111,7 +3115,7 @@ packages:
|
|||
resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -3127,7 +3131,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -3157,7 +3161,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -3178,7 +3182,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -3199,7 +3203,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -3228,7 +3232,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -3269,7 +3273,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -3289,7 +3293,7 @@ packages:
|
|||
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -3305,7 +3309,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -3332,7 +3336,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -3355,7 +3359,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -3381,7 +3385,7 @@ packages:
|
|||
resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -3395,7 +3399,7 @@ packages:
|
|||
resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -3410,7 +3414,7 @@ packages:
|
|||
resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -3425,7 +3429,7 @@ packages:
|
|||
resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -3439,7 +3443,7 @@ packages:
|
|||
resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -3453,7 +3457,7 @@ packages:
|
|||
resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -3468,7 +3472,7 @@ packages:
|
|||
resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -3484,7 +3488,7 @@ packages:
|
|||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -3590,7 +3594,7 @@ packages:
|
|||
/@storybook/addon-actions@7.4.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-SsqZr3js5NinKPnC8AeNI7Ij+Q6fIl9tRdRmSulEgjksjOg7E5S1/Wsn5Bb2CCgj7MaX6VxGyC7s3XskQtDiIQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
|
|
@ -3624,7 +3628,7 @@ packages:
|
|||
/@storybook/addon-backgrounds@7.4.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-+LHTZB/ZYMAzkyD5ZxSriBsqmsrvIaW/Nnd/BeuXGbkrVKKqM0qAKiFZAfjc2WchA1piVNy0/1Rsf+kuYCEiJw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
|
|
@ -3652,7 +3656,7 @@ packages:
|
|||
/@storybook/addon-controls@7.4.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-4lq3sycEUIsK8SUWDYc60QgF4vV9FZZ3lDr6M7j2W9bOnvGw49d2fbdlnq+bX1ZprZZ9VgglQpBAorQB3BXZRw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
|
|
@ -3684,7 +3688,7 @@ packages:
|
|||
/@storybook/addon-docs@7.4.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-dLaub+XWFq4hChw+xfuF9yYg0Txp77FUawKoAigccfjWXx+OOhRV3XTuAcknpXkYq94GWynHgUFXosXT9kbDNA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@jest/transform': 29.7.0
|
||||
|
|
@ -3718,7 +3722,7 @@ packages:
|
|||
/@storybook/addon-essentials@7.4.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-dWodufrt71TK7ELkeIvVae/x4PzECUlbOm57Iqqt4yQCyR291CgvI4PjeB8un2HbpcXCGZ+N/Oj3YkytvzBi4A==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@storybook/addon-actions': 7.4.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||
|
|
@ -3755,7 +3759,7 @@ packages:
|
|||
/@storybook/addon-links@7.4.6(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-BPygElZKX+CPI9Se6GJNk1dYc5oxuhA+vHigO1tBqhiM6VkHyFP3cvezJNQvpNYhkUnu3cxnZXb3UJnlRbPY3g==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
|
|
@ -3780,7 +3784,7 @@ packages:
|
|||
/@storybook/addon-measure@7.4.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-nCymMLaHnxv8TE3yEM1A9Tulb1NuRXRNmtsdHTkjv7P1aWCxZo8A/GZaottKe/GLT8jSRjZ+dnpYWrbAhw6wTQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
|
|
@ -3806,7 +3810,7 @@ packages:
|
|||
/@storybook/addon-outline@7.4.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-errNUblRVDLpuEaHQPr/nsrnsUkD2ARmXawkRvizgDWLIDMDJYjTON3MUCaVx3x+hlZ3I6X//G5TVcma8tCc8A==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
|
|
@ -3832,7 +3836,7 @@ packages:
|
|||
/@storybook/addon-toolbars@7.4.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-L9m2FBcKeteGq7qIYsMJr0LEfiH7Wdrv5IDcldZTn68eZUJTh1p4GdJZcOmzX1P5IFRr76hpu03iWsNlWQjpbQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
|
|
@ -3855,7 +3859,7 @@ packages:
|
|||
/@storybook/addon-viewport@7.4.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-INDtk54j7bi7NgxMfd2ATmbA0J7nAd6X8itMkLIyPuPJtx8bYHPDORyemDOd0AojgmAdTOAyUtDYdI/PFeo4Cw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
|
|
@ -3882,7 +3886,7 @@ packages:
|
|||
/@storybook/blocks@7.4.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-HxBSAeOiTZW2jbHQlo1upRWFgoMsaAyKijUFf5MwwMNIesXCuuTGZDJ3xTABwAVLK2qC9Ektfbo0CZCiPVuDRQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@storybook/channels': 7.4.6
|
||||
|
|
@ -4075,7 +4079,7 @@ packages:
|
|||
/@storybook/components@7.4.6(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-nIRBhewAgrJJVafyCzuaLx1l+YOfvvD5dOZ0JxZsxJsefOdw1jFpUqUZ5fIpQ2moyvrR0mAUFw378rBfMdHz5Q==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@radix-ui/react-select': 1.2.2(@types/react-dom@18.2.7)(@types/react@18.2.20)(react-dom@18.2.0)(react@18.2.0)
|
||||
|
|
@ -4246,7 +4250,7 @@ packages:
|
|||
/@storybook/manager-api@7.4.6(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@storybook/channels': 7.4.6
|
||||
|
|
@ -4310,7 +4314,7 @@ packages:
|
|||
/@storybook/react-dom-shim@7.4.6(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-DSq8l9FDocUF1ooVI+TF83pddj1LynE/Hv0/y8XZhc3IgJ/HkuOQuUmfz29ezgfAi9gFYUR8raTIBi3/xdoRmw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
|
|
@ -4321,7 +4325,7 @@ packages:
|
|||
resolution: {integrity: sha512-jkjnrf3FxzR5wcmebXRPflrsM4WIDjWyW/NVFJwxi5PeIOk7fE7/QAPrm4NFRUu2Q7DeuH3oLKsw8bigvUI9RA==}
|
||||
engines: {node: '>=16'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
vite: ^3.0.0 || ^4.0.0
|
||||
dependencies:
|
||||
|
|
@ -4349,7 +4353,7 @@ packages:
|
|||
resolution: {integrity: sha512-w0dVo64baFFPTGpUOWFqkKsu6pQincoymegSNgqaBd5DxEyMDRiRoTWSJHMKE9BwgE8SyWhRkP1ak1mkccSOhQ==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
|
|
@ -4388,7 +4392,7 @@ packages:
|
|||
/@storybook/router@7.4.6(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@storybook/client-logger': 7.4.6
|
||||
|
|
@ -4417,7 +4421,7 @@ packages:
|
|||
/@storybook/theming@7.4.6(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
|
||||
|
|
@ -4441,7 +4445,7 @@ packages:
|
|||
resolution: {integrity: sha512-L/y6MTLbqfHaM0faK9Yl8n5PIyW4daZrtk7NfaOT6UjgNFjOx3o4CctYew6oj90cNk5HdZQX2OZny043GxDLZw==}
|
||||
engines: {node: ^14.18 || >=16}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@storybook/builder-vite': 7.4.6(typescript@5.2.2)(vite@4.4.10)
|
||||
|
|
@ -5192,7 +5196,7 @@ packages:
|
|||
resolution: {integrity: sha512-glABtx54mh4XSaK6BNALWE3mlshPjcPwPsRj/GnOXEA7WJY/6n43iJoukbaYF3758mGZRU5Fq6gklyFjBg0yHQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
peerDependencies:
|
||||
react: ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.0.0
|
||||
dependencies:
|
||||
classnames: 2.3.2
|
||||
|
|
@ -6427,8 +6431,8 @@ packages:
|
|||
emittery: 0.10.2
|
||||
lodash-es: 4.17.21
|
||||
optionalDependencies:
|
||||
react: 17.0.2
|
||||
use-typed-event-listener: 4.0.2(react@17.0.2)(typescript@5.2.2)
|
||||
react: 18.2.0
|
||||
use-typed-event-listener: 4.0.2(react@18.2.0)(typescript@5.2.2)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
dev: true
|
||||
|
|
@ -9977,7 +9981,7 @@ packages:
|
|||
resolution: {integrity: sha512-B+28F5ucp83aQm+OxNrPkS8z0tMKaeHiy0lHJs3LqCyDQFtWuenaIrkaVTgAm1pf1AU85LXltva86hlaT17i8Q==}
|
||||
engines: {node: '>= 10'}
|
||||
peerDependencies:
|
||||
react: '>= 0.14.0'
|
||||
react: ^18.2.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
|
@ -10344,7 +10348,7 @@ packages:
|
|||
/nano-css@5.3.5(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-vSB9X12bbNu4ALBu7nigJgRViZ6ja3OU7CeuiV1zMIbXOdmkLahgtPmh3GBOlDxbKY0CitqlPdOReGlBLSp+yg==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
react: ^18.2.0
|
||||
react-dom: '*'
|
||||
dependencies:
|
||||
css-tree: 1.1.3
|
||||
|
|
@ -11430,7 +11434,7 @@ packages:
|
|||
/qrcode.react@3.1.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
|
@ -11554,7 +11558,7 @@ packages:
|
|||
/react-colorful@5.6.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react: ^18.2.0
|
||||
react-dom: '>=16.8.0'
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
|
|
@ -11600,7 +11604,7 @@ packages:
|
|||
/react-element-to-jsx-string@15.0.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==}
|
||||
peerDependencies:
|
||||
react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0
|
||||
dependencies:
|
||||
'@base2/pretty-print-object': 1.0.1
|
||||
|
|
@ -11617,7 +11621,7 @@ packages:
|
|||
/react-inspector@6.0.2(react@18.2.0):
|
||||
resolution: {integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.4 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
|
@ -11637,7 +11641,7 @@ packages:
|
|||
resolution: {integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==}
|
||||
peerDependencies:
|
||||
'@popperjs/core': ^2.0.0
|
||||
react: ^16.8.0 || ^17 || ^18
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17 || ^18
|
||||
dependencies:
|
||||
'@popperjs/core': 2.11.8
|
||||
|
|
@ -11650,7 +11654,7 @@ packages:
|
|||
/react-portal@4.2.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-fE9kOBagwmTXZ3YGRYb4gcMy+kSA+yLO0xnPankjRlfBv4uCpFXqKPfkpsGQQR15wkZ9EssnvTOl1yMzbkxhPQ==}
|
||||
peerDependencies:
|
||||
react: ^15.0.0-0 || ^16.0.0-0 || ^17.0.0-0
|
||||
react: ^18.2.0
|
||||
react-dom: ^15.0.0-0 || ^16.0.0-0 || ^17.0.0-0
|
||||
dependencies:
|
||||
prop-types: 15.8.1
|
||||
|
|
@ -11668,7 +11672,7 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -11684,7 +11688,7 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -11703,7 +11707,7 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -11718,7 +11722,7 @@ packages:
|
|||
/react-universal-interface@0.6.2(react@18.2.0)(tslib@2.6.2):
|
||||
resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
react: ^18.2.0
|
||||
tslib: '*'
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
|
|
@ -11728,7 +11732,7 @@ packages:
|
|||
/react-use-measure@2.1.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==}
|
||||
peerDependencies:
|
||||
react: '>=16.13'
|
||||
react: ^18.2.0
|
||||
react-dom: '>=16.13'
|
||||
dependencies:
|
||||
debounce: 1.2.1
|
||||
|
|
@ -11739,7 +11743,7 @@ packages:
|
|||
/react-use@17.3.1(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-hs7+tS4rRm1QLHPfanLCqXIi632tP4V7Sai1ENUP2WTufU6am++tU9uSw9YrNCFqbABiEv0ndKU1XCUcfu2tXA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^16.8.0 || ^17.0.0
|
||||
dependencies:
|
||||
'@types/js-cookie': 2.2.7
|
||||
|
|
@ -11760,16 +11764,6 @@ packages:
|
|||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/react@17.0.2:
|
||||
resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
object-assign: 4.1.1
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/react@18.2.0:
|
||||
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -13076,6 +13070,12 @@ packages:
|
|||
engines: {node: '>=14.0.0'}
|
||||
dev: true
|
||||
|
||||
/title-case@3.0.3:
|
||||
resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==}
|
||||
dependencies:
|
||||
tslib: 2.6.2
|
||||
dev: false
|
||||
|
||||
/tmp@0.2.1:
|
||||
resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==}
|
||||
engines: {node: '>=8.17.0'}
|
||||
|
|
@ -13477,7 +13477,7 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -13487,20 +13487,10 @@ packages:
|
|||
tslib: 2.6.2
|
||||
dev: true
|
||||
|
||||
/use-deep-compare@1.1.0(react@17.0.2):
|
||||
resolution: {integrity: sha512-6yY3zmKNCJ1jjIivfZMZMReZjr8e6iC6Uqtp701jvWJ6ejC/usXD+JjmslZDPJQgX8P4B1Oi5XSLHkOLeYSJsA==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
dependencies:
|
||||
dequal: 1.0.0
|
||||
react: 17.0.2
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/use-deep-compare@1.1.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-6yY3zmKNCJ1jjIivfZMZMReZjr8e6iC6Uqtp701jvWJ6ejC/usXD+JjmslZDPJQgX8P4B1Oi5XSLHkOLeYSJsA==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react: ^18.2.0
|
||||
dependencies:
|
||||
dequal: 1.0.0
|
||||
react: 18.2.0
|
||||
|
|
@ -13509,7 +13499,7 @@ packages:
|
|||
/use-resize-observer@9.1.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==}
|
||||
peerDependencies:
|
||||
react: 16.8.0 - 18
|
||||
react: ^18.2.0
|
||||
react-dom: 16.8.0 - 18
|
||||
dependencies:
|
||||
'@juggle/resize-observer': 3.3.1
|
||||
|
|
@ -13522,7 +13512,7 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -13536,32 +13526,15 @@ packages:
|
|||
/use-sync-external-store@1.2.0(react@18.2.0):
|
||||
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react: ^18.2.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
|
||||
/use-typed-event-listener@4.0.2(react@17.0.2)(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-AhjRw+qg5t7OWg7en2Q4XDCdi2iyhALFl71tITL3FeZqt/jD1Qa6KnbH/UolARtID1Gd0IElizbCgolv3ZbUFA==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
react: '>=16.14.0'
|
||||
typescript: '>=4.1.2'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.11
|
||||
react: 17.0.2
|
||||
typescript: 5.2.2
|
||||
use-deep-compare: 1.1.0(react@17.0.2)
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/use-typed-event-listener@4.0.2(react@18.2.0)(typescript@5.2.2):
|
||||
resolution: {integrity: sha512-AhjRw+qg5t7OWg7en2Q4XDCdi2iyhALFl71tITL3FeZqt/jD1Qa6KnbH/UolARtID1Gd0IElizbCgolv3ZbUFA==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
peerDependencies:
|
||||
react: '>=16.14.0'
|
||||
react: ^18.2.0
|
||||
typescript: '>=4.1.2'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
|
|
@ -13634,7 +13607,7 @@ packages:
|
|||
engines: {node: '>=12.20.0'}
|
||||
peerDependencies:
|
||||
'@types/react': '>=16.8'
|
||||
react: '>=16.8'
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
|
@ -14313,7 +14286,7 @@ packages:
|
|||
resolution: {integrity: sha512-/WfLJuXiEJimt61KGMHebrFBwckkCHGhAgVXTgPQHl6IMzjqm6MREb1OnDSnCRiSmRdhgdFCctceg6tSm79hiw==}
|
||||
engines: {node: '>=12.7.0'}
|
||||
peerDependencies:
|
||||
react: '>=16.8'
|
||||
react: ^18.2.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//@ts-check
|
||||
|
||||
import { polyfillNode } from 'esbuild-plugin-polyfill-node'
|
||||
import { join, dirname } from 'path'
|
||||
import { join, dirname, basename } from 'path'
|
||||
import * as fs from 'fs'
|
||||
import { filesize } from 'filesize'
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ const plugins = [
|
|||
build.onResolve({
|
||||
filter: /.*/,
|
||||
}, async ({ path, ...rest }) => {
|
||||
if (['.woff', '.woff2', '.ttf'].some(ext => path.endsWith(ext))) {
|
||||
if (['.woff', '.woff2', '.ttf'].some(ext => path.endsWith(ext)) || path.startsWith('extra-textures/')) {
|
||||
return {
|
||||
path,
|
||||
namespace: 'assets',
|
||||
|
|
@ -63,6 +63,7 @@ const plugins = [
|
|||
|
||||
build.onEnd(async ({ metafile, outputFiles }) => {
|
||||
// write outputFiles
|
||||
//@ts-ignore
|
||||
for (const file of outputFiles) {
|
||||
await fs.promises.writeFile(file.path, file.contents)
|
||||
}
|
||||
|
|
@ -89,7 +90,7 @@ const plugins = [
|
|||
}, async ({ resolveDir, path, importer, kind, pluginData }) => {
|
||||
if (pluginData?.__internal) return
|
||||
if (!resolveDir.startsWith(process.cwd())) {
|
||||
const redirected = await build.resolve(path, { kind, importer, resolveDir: process.cwd(), pluginData: {__internal: true}, });
|
||||
const redirected = await build.resolve(path, { kind, importer, resolveDir: process.cwd(), pluginData: { __internal: true }, })
|
||||
return redirected
|
||||
}
|
||||
// disallow imports from outside the root directory to ensure modules are resolved from node_modules of this workspace
|
||||
|
|
@ -109,7 +110,9 @@ const plugins = [
|
|||
build.onStart(() => {
|
||||
time = Date.now()
|
||||
})
|
||||
build.onEnd(({ errors, outputFiles, metafile, warnings }) => {
|
||||
build.onEnd(({ errors, outputFiles: _outputFiles, metafile, warnings }) => {
|
||||
/** @type {any} */
|
||||
const outputFiles = _outputFiles
|
||||
const elapsed = Date.now() - time
|
||||
|
||||
if (errors.length) {
|
||||
|
|
@ -230,6 +233,31 @@ const plugins = [
|
|||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'react-displayname',
|
||||
setup (build) {
|
||||
build.onLoad({
|
||||
filter: /.tsx$/,
|
||||
}, async ({ path }) => {
|
||||
let file = await fs.promises.readFile(path, 'utf8')
|
||||
const fileName = basename(path, '.tsx')
|
||||
let replaced = false
|
||||
const varName = `__${fileName}_COMPONENT`
|
||||
file = file.replace(/export default /, () => {
|
||||
replaced = true
|
||||
return `const ${varName} = `
|
||||
})
|
||||
if (replaced) {
|
||||
file += `;${varName}.displayName = '${fileName}';export default ${varName};`
|
||||
}
|
||||
|
||||
return {
|
||||
contents: file,
|
||||
loader: 'tsx',
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
polyfillNode({
|
||||
polyfills: {
|
||||
fs: false,
|
||||
|
|
|
|||
43
src/basicSounds.ts
Normal file
43
src/basicSounds.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { options } from './optionsStorage'
|
||||
|
||||
let audioContext: AudioContext
|
||||
const sounds: Record<string, any> = {}
|
||||
|
||||
// load as many resources on page load as possible instead on demand as user can disable internet connection after he thinks the page is loaded
|
||||
const loadingSounds = []
|
||||
const convertedSounds = []
|
||||
export async function loadSound (path: string) {
|
||||
if (loadingSounds.includes(path)) return
|
||||
loadingSounds.push(path)
|
||||
const res = await window.fetch(path)
|
||||
const data = await res.arrayBuffer()
|
||||
|
||||
sounds[path] = data
|
||||
loadingSounds.splice(loadingSounds.indexOf(path), 1)
|
||||
}
|
||||
|
||||
export async function playSound (path) {
|
||||
audioContext ??= new window.AudioContext()
|
||||
|
||||
for (const [soundName, sound] of Object.entries(sounds)) {
|
||||
if (convertedSounds.includes(soundName)) continue
|
||||
sounds[soundName] = await audioContext.decodeAudioData(sound)
|
||||
convertedSounds.push(soundName)
|
||||
}
|
||||
|
||||
const volume = options.volume / 100
|
||||
|
||||
const soundBuffer = sounds[path]
|
||||
if (!soundBuffer) {
|
||||
console.warn(`Sound ${path} not loaded`)
|
||||
return
|
||||
}
|
||||
|
||||
const gainNode = audioContext.createGain()
|
||||
const source = audioContext.createBufferSource()
|
||||
source.buffer = soundBuffer
|
||||
source.connect(gainNode)
|
||||
gainNode.connect(audioContext.destination)
|
||||
gainNode.gain.value = volume
|
||||
source.start(0)
|
||||
}
|
||||
|
|
@ -4,9 +4,8 @@ import { classMap } from 'lit/directives/class-map.js'
|
|||
import { LitElement, html, css } from 'lit'
|
||||
import { isCypress } from './utils'
|
||||
import { getBuiltinCommandsList, tryHandleBuiltinCommand } from './builtinCommands'
|
||||
import { notification } from './menus/notification'
|
||||
import { options } from './optionsStorage'
|
||||
import { activeModalStack, hideCurrentModal, showModal, miscUiState } from './globalState'
|
||||
import { activeModalStack, hideCurrentModal, showModal, miscUiState, notification } from './globalState'
|
||||
import { formatMessage } from './botUtils'
|
||||
import { getColorShadow, messageFormatStylesMap } from './react/MessageFormatted'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { promisify } from 'util'
|
||||
import * as nbt from 'prismarine-nbt'
|
||||
import { showNotification } from './menus/notification'
|
||||
import { openWorldDirectory, openWorldZip } from './browserfs'
|
||||
import { isGameActive } from './globalState'
|
||||
import { isGameActive, showNotification } from './globalState'
|
||||
|
||||
const parseNbt = promisify(nbt.parse)
|
||||
window.nbt = nbt
|
||||
|
|
|
|||
17
src/flyingSquidUtils.ts
Normal file
17
src/flyingSquidUtils.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import * as crypto from 'crypto'
|
||||
import UUID from 'uuid-1345'
|
||||
|
||||
|
||||
// https://github.com/PrismarineJS/node-minecraft-protocol/blob/cf1f67117d586b5e6e21f0d9602da12e9fcf46b6/src/server/login.js#L170
|
||||
function javaUUID (s: string) {
|
||||
const hash = crypto.createHash('md5')
|
||||
hash.update(s, 'utf8')
|
||||
const buffer = hash.digest()
|
||||
buffer[6] = (buffer[6] & 15) | 48
|
||||
buffer[8] = (buffer[8] & 63) | 128
|
||||
return buffer
|
||||
}
|
||||
|
||||
export function nameToMcOfflineUUID (name) {
|
||||
return (new UUID(javaUUID('OfflinePlayer:' + name))).toString()
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { proxy, ref, subscribe } from 'valtio'
|
||||
import { pointerLock } from './utils'
|
||||
import { options } from './optionsStorage'
|
||||
import { OptionsGroupType, options } from './optionsStorage'
|
||||
|
||||
// todo: refactor structure with support of hideNext=false
|
||||
|
||||
|
|
@ -27,8 +27,6 @@ subscribe(activeModalStack, () => {
|
|||
if (activeModalStack.length === 0) {
|
||||
if (isGameActive(false)) {
|
||||
void pointerLock.requestPointerLock()
|
||||
} else {
|
||||
showModal(document.getElementById('title-screen'))
|
||||
}
|
||||
} else {
|
||||
document.exitPointerLock?.()
|
||||
|
|
@ -94,6 +92,10 @@ export const hideCurrentModal = (_data = undefined, onHide = undefined) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const openOptionsMenu = (group: OptionsGroupType) => {
|
||||
showModal({ reactType: `options-${group}` })
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
export const currentContextMenu = proxy({ items: [] as ContextMenuItem[] | null, x: 0, y: 0 })
|
||||
|
|
@ -146,6 +148,19 @@ export const gameAdditionalState = proxy({
|
|||
|
||||
window.gameAdditionalState = gameAdditionalState
|
||||
|
||||
// rename current (non-stackable) notification to one-time (system) notification
|
||||
const initialNotification = {
|
||||
show: false,
|
||||
autoHide: true,
|
||||
message: '',
|
||||
type: 'info',
|
||||
}
|
||||
export const notification = proxy(initialNotification)
|
||||
|
||||
export const showNotification = (/** @type {Partial<typeof notification>} */newNotification) => {
|
||||
Object.assign(notification, { show: true, ...newNotification }, initialNotification)
|
||||
}
|
||||
|
||||
const savePlayers = () => {
|
||||
if (!window.localServer) return
|
||||
for (const player of window.localServer.players) {
|
||||
|
|
@ -167,7 +182,7 @@ window.inspectPlayer = () => require('fs').promises.readFile('/world/playerdata/
|
|||
// todo move from global state
|
||||
window.addEventListener('beforeunload', (event) => {
|
||||
// todo-low maybe exclude chat?
|
||||
if (!isGameActive(true) && activeModalStack.at(-1)?.elem.id !== 'chat') return
|
||||
if (!isGameActive(true) && activeModalStack.at(-1)?.elem?.id !== 'chat') return
|
||||
if (sessionStorage.lastReload && !options.preventDevReloadWhilePlaying) return
|
||||
if (!options.closeConfirmation) return
|
||||
|
||||
|
|
|
|||
4
src/globals.d.ts
vendored
4
src/globals.d.ts
vendored
|
|
@ -47,6 +47,10 @@ 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
|
||||
|
|
|
|||
18
src/index.ts
18
src/index.ts
|
|
@ -21,11 +21,7 @@ import './menus/play_screen'
|
|||
import './menus/pause_screen'
|
||||
import './menus/loading_or_error_screen'
|
||||
import './menus/keybinds_screen'
|
||||
import './menus/options_screen'
|
||||
import './menus/advanced_options_screen'
|
||||
import { notification } from './menus/notification'
|
||||
import './menus/title_screen'
|
||||
import { initWithRenderer, statsEnd, statsStart } from './rightTopStats'
|
||||
import { initWithRenderer, statsEnd, statsStart } from './topRightStats'
|
||||
|
||||
import { options, watchValue } from './optionsStorage'
|
||||
import './reactUi.jsx'
|
||||
|
|
@ -57,7 +53,8 @@ import {
|
|||
isGameActive,
|
||||
miscUiState,
|
||||
gameAdditionalState,
|
||||
resetStateAfterDisconnect
|
||||
resetStateAfterDisconnect,
|
||||
notification
|
||||
} from './globalState'
|
||||
|
||||
import {
|
||||
|
|
@ -197,7 +194,7 @@ async function main () {
|
|||
const connectSingleplayer = (serverOverrides = {}) => {
|
||||
void connect({ singleplayer: true, username: options.localUsername, password: '', serverOverrides })
|
||||
}
|
||||
document.querySelector('#title-screen').addEventListener('singleplayer', (e) => {
|
||||
window.addEventListener('singleplayer', (e) => {
|
||||
//@ts-expect-error
|
||||
connectSingleplayer(e.detail)
|
||||
})
|
||||
|
|
@ -246,7 +243,7 @@ async function connect (connectOptions: {
|
|||
const p2pMultiplayer = !!connectOptions.peerId
|
||||
miscUiState.singleplayer = singeplayer
|
||||
miscUiState.flyingSquid = singeplayer || p2pMultiplayer
|
||||
const { renderDistance, maxMultiplayerRenderDistance } = options
|
||||
const { renderDistance, maxMultiplayerRenderDistance = renderDistance } = options
|
||||
const server = cleanConnectIp(connectOptions.server, '25565')
|
||||
const proxy = cleanConnectIp(connectOptions.proxy, undefined)
|
||||
const { username, password } = connectOptions
|
||||
|
|
@ -719,15 +716,14 @@ window.addEventListener('keydown', (e) => {
|
|||
window.addEventListener('keydown', (e) => {
|
||||
if (e.code === 'F11') {
|
||||
e.preventDefault()
|
||||
goFullscreen(true)
|
||||
void goFullscreen(true)
|
||||
}
|
||||
if (e.code === 'KeyL' && e.altKey) {
|
||||
console.clear()
|
||||
}
|
||||
})
|
||||
|
||||
addPanoramaCubeMap()
|
||||
showModal(document.getElementById('title-screen'))
|
||||
void addPanoramaCubeMap()
|
||||
void main()
|
||||
downloadAndOpenFile().then((downloadAction) => {
|
||||
if (downloadAction) return
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import * as nbt from 'prismarine-nbt'
|
|||
import { proxy } from 'valtio'
|
||||
import { gzip } from 'node-gzip'
|
||||
import { options } from './optionsStorage'
|
||||
import { nameToMcOfflineUUID } from './utils'
|
||||
import { nameToMcOfflineUUID } from './flyingSquidUtils'
|
||||
import { forceCachedDataPaths } from './browserfs'
|
||||
|
||||
const parseNbt = promisify(nbt.parse)
|
||||
|
|
@ -124,7 +124,7 @@ export const loadSave = async (root = '/world') => {
|
|||
}
|
||||
|
||||
fsState.saveLoaded = true
|
||||
document.querySelector('#title-screen').dispatchEvent(new CustomEvent('singleplayer', {
|
||||
window.dispatchEvent(new CustomEvent('singleplayer', {
|
||||
// todo check gamemode level.dat data etc
|
||||
detail: {
|
||||
version,
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
//@ts-check
|
||||
const { html, css, LitElement } = require('lit')
|
||||
const { subscribe } = require('valtio')
|
||||
const { hideCurrentModal } = require('../globalState')
|
||||
const { getScreenRefreshRate } = require('../utils')
|
||||
const { options } = require('../optionsStorage')
|
||||
const { commonCss, openURL } = require('./components/common')
|
||||
|
||||
class AdvancedOptionsScreen extends LitElement {
|
||||
/** @type {null | number} */
|
||||
frameLimitMax = null
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
subscribe(options, () => {
|
||||
this.requestUpdate()
|
||||
})
|
||||
}
|
||||
|
||||
static get styles () {
|
||||
return css`
|
||||
${commonCss}
|
||||
.title {
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: calc(100% / 6 - 6px);
|
||||
left: 50%;
|
||||
width: 310px;
|
||||
gap: 4px 0;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
gap: 0 10px;
|
||||
height: 20px;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div class="${'dirt-bg'}"></div>
|
||||
|
||||
<p class="title">Advanced Options</p>
|
||||
<main>
|
||||
<div class="wrapper">
|
||||
<pmui-button pmui-width="150px" pmui-label=${`Always Show Mobile Controls: ${options.alwaysShowMobileControls ? 'ON' : 'OFF'}`} @pmui-click=${() => {
|
||||
options.alwaysShowMobileControls = !options.alwaysShowMobileControls
|
||||
}
|
||||
}></pmui-button>
|
||||
<!-- todo rename button, also might be unstable -->
|
||||
<pmui-button pmui-width="150px" pmui-label="Guide: Disable VSync" @click=${() => openURL('https://gist.github.com/zardoy/6e5ce377d2b4c1e322e660973da069cd')}></pmui-button>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-slider .disabled=${!this.frameLimitMax} pmui-label="Frame Limit" .valueDisplay=${options.frameLimit || 'VSync'} pmui-value="${options.frameLimit || this.frameLimitMax + 1}"
|
||||
pmui-type="${options.frameLimit ? 'fps' : ''}" pmui-min="20" pmui-max="${this.frameLimitMax + 1}" @input=${(e) => {
|
||||
const newVal = e.target.value
|
||||
options.frameLimit = newVal > this.frameLimitMax ? false : newVal
|
||||
this.requestUpdate()
|
||||
}}></pmui-slider>
|
||||
<pmui-button pmui-width="20px" pmui-icon="pixelarticons:lock-open" @click=${async () => {
|
||||
const rate = await getScreenRefreshRate()
|
||||
this.frameLimitMax = rate
|
||||
this.requestUpdate()
|
||||
}}></pmui-button>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-slider pmui-width="150px" pmui-label="Touch Buttons Size" pmui-value="${options.touchButtonsSize}" pmui-type="%" pmui-min="20" pmui-max="100" @input=${(e) => {
|
||||
options.touchButtonsSize = +e.target.value
|
||||
}}></pmui-slider>
|
||||
<pmui-button pmui-width="150px" pmui-label="${`Use Dedicated GPU: ${options.highPerformanceGpu ? 'ON' : 'OFF'}`}" title="Changing requires page reload. Only for those who have two GPUs e.g. on laptops" @pmui-click=${() => {
|
||||
options.highPerformanceGpu = !options.highPerformanceGpu
|
||||
}}></pmui-button>
|
||||
</div>
|
||||
|
||||
<pmui-button pmui-width="200px" pmui-label="Done" @pmui-click=${() => hideCurrentModal()}></pmui-button>
|
||||
</main>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-advanced-optionsscreen', AdvancedOptionsScreen)
|
||||
|
|
@ -1,49 +1,7 @@
|
|||
//@ts-check
|
||||
import { LitElement, html, css, unsafeCSS } from 'lit'
|
||||
import widgetsGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/widgets.png'
|
||||
import { options } from '../../optionsStorage'
|
||||
|
||||
let audioContext
|
||||
/** @type {Record<string, any>} */
|
||||
const sounds = {}
|
||||
|
||||
// load as many resources on page load as possible instead on demand as user can disable internet connection after he thinks the page is loaded
|
||||
const loadingSounds = []
|
||||
const convertedSounds = []
|
||||
async function loadSound (path) {
|
||||
loadingSounds.push(path)
|
||||
const res = await window.fetch(path)
|
||||
const data = await res.arrayBuffer()
|
||||
|
||||
sounds[path] = data
|
||||
loadingSounds.splice(loadingSounds.indexOf(path), 1)
|
||||
}
|
||||
|
||||
export async function playSound (path) {
|
||||
audioContext ??= new window.AudioContext()
|
||||
|
||||
for (const [soundName, sound] of Object.entries(sounds)) {
|
||||
if (convertedSounds.includes(soundName)) continue
|
||||
sounds[soundName] = await audioContext.decodeAudioData(sound)
|
||||
convertedSounds.push(soundName)
|
||||
}
|
||||
|
||||
const volume = options.volume / 100
|
||||
|
||||
const soundBuffer = sounds[path]
|
||||
if (!soundBuffer) {
|
||||
console.warn(`Sound ${path} not loaded`)
|
||||
return
|
||||
}
|
||||
|
||||
const gainNode = audioContext.createGain()
|
||||
const source = audioContext.createBufferSource()
|
||||
source.buffer = soundBuffer
|
||||
source.connect(gainNode)
|
||||
gainNode.connect(audioContext.destination)
|
||||
gainNode.gain.value = volume
|
||||
source.start(0)
|
||||
}
|
||||
import { playSound, loadSound } from '../../basicSounds'
|
||||
|
||||
class Button extends LitElement {
|
||||
static get styles () {
|
||||
|
|
|
|||
|
|
@ -2,23 +2,8 @@
|
|||
|
||||
// create lit element
|
||||
const { LitElement, html, css } = require('lit')
|
||||
const { proxy, subscribe } = require('valtio/vanilla')
|
||||
|
||||
// move to globalState?
|
||||
// rename current (non-stackable) notification to one-time (system) notification
|
||||
const initialNotification = {
|
||||
show: false,
|
||||
autoHide: true,
|
||||
message: '',
|
||||
type: 'info',
|
||||
}
|
||||
export const notification = proxy(initialNotification)
|
||||
|
||||
export const showNotification = (/** @type {Partial<typeof notification>} */newNotification) => {
|
||||
Object.assign(notification, { show: true, ...newNotification }, initialNotification)
|
||||
}
|
||||
|
||||
window.notification = notification
|
||||
const { subscribe } = require('valtio')
|
||||
const { notification } = require('../globalState')
|
||||
|
||||
class Notification extends LitElement {
|
||||
static get properties () {
|
||||
|
|
|
|||
|
|
@ -1,156 +0,0 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
const { subscribe } = require('valtio')
|
||||
const { subscribeKey } = require('valtio/utils')
|
||||
const { showModal, hideCurrentModal, isGameActive, miscUiState } = require('../globalState')
|
||||
const { toNumber, openFilePicker, setLoadingScreenStatus } = require('../utils')
|
||||
const { options, watchValue } = require('../optionsStorage')
|
||||
const { getResourcePackName, uninstallTexturePack, resourcePackState } = require('../texturePack')
|
||||
const { fsState } = require('../loadSave')
|
||||
const { commonCss, isMobile } = require('./components/common')
|
||||
|
||||
class OptionsScreen extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
${commonCss}
|
||||
.title {
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: calc(100% / 6 - 6px);
|
||||
left: 50%;
|
||||
width: 310px;
|
||||
gap: 4px 0;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
gap: 0 10px;
|
||||
height: 20px;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
watchValue(options, o => {
|
||||
document.documentElement.style.setProperty('--chatScale', `${o.chatScale / 100}`)
|
||||
document.documentElement.style.setProperty('--chatWidth', `${o.chatWidth}px`)
|
||||
document.documentElement.style.setProperty('--chatHeight', `${o.chatHeight}px`)
|
||||
document.documentElement.style.setProperty('--guiScale', `${o.guiScale}`)
|
||||
})
|
||||
|
||||
subscribe(options, () => {
|
||||
this.requestUpdate()
|
||||
})
|
||||
subscribeKey(miscUiState, 'singleplayer', () => {
|
||||
this.requestUpdate()
|
||||
})
|
||||
subscribeKey(resourcePackState, 'resourcePackInstalled', () => {
|
||||
this.requestUpdate()
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div class="${isGameActive(false) ? 'bg' : 'dirt-bg'}"></div>
|
||||
|
||||
<p class="title">Options</p>
|
||||
|
||||
<main>
|
||||
<div class="wrapper">
|
||||
<pmui-slider pmui-label="Mouse Sensitivity X" pmui-value="${options.mouseSensX}" pmui-min="1" pmui-max="100" @input=${(e) => {
|
||||
options.mouseSensX = +e.target.value
|
||||
}}></pmui-slider>
|
||||
<pmui-slider pmui-label="Mouse Sensitivity Y" pmui-value="${options.mouseSensY}" pmui-min="1" pmui-max="100" @input=${(e) => {
|
||||
options.mouseSensY = +e.target.value
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-slider pmui-label="Chat Width" pmui-value="${options.chatWidth}" pmui-min="0" pmui-max="320" pmui-type="px" @input=${(e) => {
|
||||
options.chatWidth = +e.target.value
|
||||
}}></pmui-slider>
|
||||
<pmui-slider pmui-label="Chat Height" pmui-value="${options.chatHeight}" pmui-min="0" pmui-max="180" pmui-type="px" @input=${(e) => {
|
||||
options.chatHeight = +e.target.value
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-slider pmui-label="Chat Scale" pmui-value="${options.chatScale}" pmui-min="0" pmui-max="100" @input=${(e) => {
|
||||
options.chatScale = +e.target.value
|
||||
}}></pmui-slider>
|
||||
<pmui-slider pmui-label="Sound Volume" pmui-value="${options.volume}" pmui-min="0" pmui-max="100" @input=${(e) => {
|
||||
options.volume = +e.target.value
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-button .disabled=${true} pmui-width="150px" pmui-label="Key Binds" @pmui-click=${() => showModal(document.getElementById('keybinds-screen'))}></pmui-button>
|
||||
<pmui-slider pmui-label="Gui Scale" pmui-value="${options.guiScale}" pmui-min="1" pmui-max="4" pmui-type="" @change=${(e) => {
|
||||
options.guiScale = +e.target.value
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-slider pmui-label="Render Distance" pmui-value="${options.renderDistance}" .disabled="${isGameActive(false) && !miscUiState.singleplayer ? 'Can be changed only from main menu for now' : undefined}" pmui-min="2" pmui-max="${miscUiState.singleplayer ? 16 : 6}" pmui-type=" chunks" @change=${(e) => {
|
||||
options.renderDistance = +e.target.value
|
||||
}}></pmui-slider>
|
||||
<pmui-slider pmui-label="Field of View" pmui-value="${options.fov}" pmui-min="30" pmui-max="110" pmui-type="" @input=${(e) => {
|
||||
options.fov = +e.target.value
|
||||
}}></pmui-slider>
|
||||
</div>
|
||||
|
||||
<div class="wrapper">
|
||||
<pmui-button pmui-width="150px" pmui-label=${'Advanced'} @pmui-click=${() => {
|
||||
showModal(document.querySelector('pmui-advanced-optionsscreen'))
|
||||
}
|
||||
}></pmui-button>
|
||||
<pmui-button pmui-width="150px" pmui-label=${'Mouse Raw Input: ' + (options.mouseRawInput ? 'ON' : 'OFF')} title="Wether to disable any mouse acceleration (MC does it by default)" @pmui-click=${() => {
|
||||
options.mouseRawInput = !options.mouseRawInput
|
||||
}
|
||||
}></pmui-button>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-button title="Auto Fullscreen allows you to use Ctrl+W and Escape without delays" .disabled="${navigator['keyboard'] ? undefined : 'Your browser doesn\'t support keyboard lock API'}" pmui-width="150px" pmui-label=${'Auto Fullscreen: ' + (options.autoFullScreen ? 'ON' : 'OFF')} @pmui-click=${() => {
|
||||
options.autoFullScreen = !options.autoFullScreen
|
||||
}
|
||||
}></pmui-button>
|
||||
<!-- todo also allow to remap f11 -->
|
||||
<pmui-button title="Exit fullscreen (not recommended, also you can always do it with F11)" pmui-width="150px" pmui-label=${'Auto Exit Fullscreen: ' + (options.autoExitFullscreen ? 'ON' : 'OFF')} @pmui-click=${() => {
|
||||
options.autoExitFullscreen = !options.autoExitFullscreen
|
||||
}
|
||||
}></pmui-button>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<pmui-button pmui-width="150px" pmui-label=${'Resource Pack: ' + (resourcePackState.resourcePackInstalled ? 'ON' : 'OFF')} @pmui-click=${async () => {
|
||||
if (resourcePackState.resourcePackInstalled) {
|
||||
const resourcePackName = await getResourcePackName()
|
||||
if (confirm(`Uninstall ${resourcePackName} resource pack?`)) {
|
||||
// todo make hidable
|
||||
setLoadingScreenStatus('Uninstalling texturepack...')
|
||||
await uninstallTexturePack()
|
||||
setLoadingScreenStatus(undefined)
|
||||
}
|
||||
} else {
|
||||
if (!fsState.inMemorySave && isGameActive(false)) {
|
||||
alert('Unable to install resource pack in loaded save for now')
|
||||
return
|
||||
}
|
||||
openFilePicker('resourcepack')
|
||||
}
|
||||
}}></pmui-button>
|
||||
</div>
|
||||
|
||||
<pmui-button pmui-width="200px" pmui-label="Done" @pmui-click=${() => hideCurrentModal()}></pmui-button>
|
||||
</main>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-optionsscreen', OptionsScreen)
|
||||
|
|
@ -2,12 +2,11 @@
|
|||
const { LitElement, html, css } = require('lit')
|
||||
const { subscribe } = require('valtio')
|
||||
const { subscribeKey } = require('valtio/utils')
|
||||
const { hideCurrentModal, showModal, miscUiState } = require('../globalState')
|
||||
const { hideCurrentModal, showModal, miscUiState, notification, openOptionsMenu } = require('../globalState')
|
||||
const { fsState } = require('../loadSave')
|
||||
const { saveWorld } = require('../builtinCommands')
|
||||
const { disconnect } = require('../utils')
|
||||
const { closeWan, openToWanAndCopyJoinLink, getJoinLink } = require('../localServerMultiplayer')
|
||||
const { notification } = require('./notification')
|
||||
const { openURL } = require('./components/common')
|
||||
|
||||
class PauseScreen extends LitElement {
|
||||
|
|
@ -77,7 +76,7 @@ class PauseScreen extends LitElement {
|
|||
<pmui-button pmui-width="98px" pmui-label="GitHub" @pmui-click=${() => openURL(process.env.GITHUB_URL)}></pmui-button>
|
||||
<pmui-button pmui-width="98px" pmui-label="Discord" @pmui-click=${() => openURL('https://discord.gg/4Ucm684Fq3')}></pmui-button>
|
||||
</div>
|
||||
<pmui-button pmui-width="204px" pmui-label="Options" @pmui-click=${() => showModal(document.getElementById('options-screen'))}></pmui-button>
|
||||
<pmui-button pmui-width="204px" pmui-label="Options" @pmui-click=${() => openOptionsMenu('main')}></pmui-button>
|
||||
<!-- todo use qr icon (full pixelarticons package) -->
|
||||
<!-- todo also display copy link button when opened -->
|
||||
${joinButton ? html`
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ class PlayScreen extends LitElement {
|
|||
const getParam = (localStorageKey, qs = localStorageKey) => {
|
||||
const qsValue = qs ? params.get(qs) : undefined
|
||||
if (qsValue) {
|
||||
document.getElementById('title-screen').style.display = 'none'
|
||||
this.style.display = 'block'
|
||||
}
|
||||
return qsValue || window.localStorage.getItem(localStorageKey)
|
||||
|
|
@ -195,7 +194,6 @@ class PlayScreen extends LitElement {
|
|||
const server = `${this.server}${this.serverport && `:${this.serverport}`}`
|
||||
const proxy = this.proxy && `${this.proxy}${this.proxyport && `:${this.proxyport}`}`
|
||||
|
||||
document.getElementById('title-screen').style.display = 'none'
|
||||
window.localStorage.setItem('username', this.username)
|
||||
window.localStorage.setItem('password', this.password)
|
||||
window.localStorage.setItem('server', server)
|
||||
|
|
|
|||
|
|
@ -1,198 +0,0 @@
|
|||
const fs = require('fs')
|
||||
const { LitElement, html, css, unsafeCSS } = require('lit')
|
||||
|
||||
const mcImage = require('minecraft-assets/minecraft-assets/data/1.17.1/gui/title/minecraft.png')
|
||||
const { fsState } = require('../loadSave')
|
||||
const { showModal } = require('../globalState')
|
||||
const { openWorldDirectory, openWorldZip } = require('../browserfs')
|
||||
const { options } = require('../optionsStorage')
|
||||
const defaultLocalServerOptions = require('../defaultLocalServerOptions')
|
||||
const { openFilePicker } = require('../utils')
|
||||
const { openURL } = require('./components/common')
|
||||
|
||||
// const SUPPORT_WORLD_LOADING = !!window.showDirectoryPicker
|
||||
const SUPPORT_WORLD_LOADING = true
|
||||
|
||||
class TitleScreen extends LitElement {
|
||||
static get styles () {
|
||||
return css`
|
||||
.game-title {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: calc(50% - 137px);
|
||||
}
|
||||
|
||||
.game-title .minec {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-image: url('${unsafeCSS(mcImage)}');
|
||||
background-size: 256px;
|
||||
width: 155px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.game-title .raft {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 155px;
|
||||
background-image: url('${unsafeCSS(mcImage)}');
|
||||
background-size: 256px;
|
||||
width: 155px;
|
||||
height: 44px;
|
||||
background-position-y: -45px;
|
||||
}
|
||||
|
||||
.game-title .edition {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 37px;
|
||||
left: calc(88px + 5px);
|
||||
background-image: url('extra-textures/edition.png');
|
||||
background-size: 128px;
|
||||
width: 88px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.splash {
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
left: 227px;
|
||||
color: #ff0;
|
||||
transform: translate(-50%, -50%) rotateZ(-20deg) scale(1);
|
||||
width: max-content;
|
||||
text-shadow: 1px 1px #220;
|
||||
font-size: 10px;
|
||||
animation: splashAnim 400ms infinite alternate linear;
|
||||
}
|
||||
|
||||
@keyframes splashAnim {
|
||||
to {
|
||||
transform: translate(-50%, -50%) rotateZ(-20deg) scale(1.07);
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px 0;
|
||||
position: absolute;
|
||||
top: calc(25% + 48px);
|
||||
left: 50%;
|
||||
width: 200px;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
|
||||
.menu-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0 4px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bottom-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 1px;
|
||||
width: calc(100% - 2px);
|
||||
color: white;
|
||||
text-shadow: 1px 1px #222;
|
||||
font-size: 10px;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
versionStatus: {
|
||||
type: String
|
||||
},
|
||||
versionTitle: {
|
||||
type: String
|
||||
},
|
||||
isOutdated: {
|
||||
type: Boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.versionStatus = ''
|
||||
this.versionTitle = ''
|
||||
this.isOutdated = false
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this.versionStatus = '(dev)'
|
||||
} else {
|
||||
fetch('./version.txt').then(async (f) => {
|
||||
if (f.status === 404) return
|
||||
const contents = await f.text()
|
||||
this.isOutdated = contents === process.env.BUILD_VERSION
|
||||
this.versionStatus = `(${this.isOutdated ? 'latest' : 'new version available'})`
|
||||
this.versionTitle = `Loaded: ${process.env.BUILD_VERSION}. Remote: ${contents}`
|
||||
}, () => { })
|
||||
}
|
||||
}
|
||||
|
||||
reload () {
|
||||
navigator.serviceWorker.getRegistration().then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
return html`
|
||||
<div class="game-title">
|
||||
<div class="minec"></div>
|
||||
<div class="raft"></div>
|
||||
<div class="edition"></div>
|
||||
<span class="splash">Prismarine is a beautiful block</span>
|
||||
</div>
|
||||
|
||||
<div class="menu">
|
||||
<pmui-button pmui-width="200px" pmui-label="Connect to server" pmui-test-id="connect-screen-button" @pmui-click=${() => showModal(document.getElementById('play-screen'))}></pmui-button>
|
||||
<div style="display:flex;justify-content: space-between;">
|
||||
<pmui-button pmui-width="${SUPPORT_WORLD_LOADING ? '170px' : '200px'}" pmui-test-id="singleplayer-button" pmui-label="Singleplayer" @pmui-click=${() => {
|
||||
this.style.display = 'none'
|
||||
fsState.isReadonly = false
|
||||
fsState.syncFs = true
|
||||
fsState.inMemorySave = true
|
||||
const notFirstTime = fs.existsSync('./world/level.dat')
|
||||
if (notFirstTime && !options.localServerOptions.version) {
|
||||
options.localServerOptions.version = '1.16.1' // legacy version
|
||||
} else {
|
||||
options.localServerOptions.version ??= defaultLocalServerOptions.version
|
||||
}
|
||||
this.dispatchEvent(new window.CustomEvent('singleplayer', {}))
|
||||
}}></pmui-button>
|
||||
${SUPPORT_WORLD_LOADING ? html`<pmui-button pmui-test-id="select-file-folder" pmui-icon="pixelarticons:folder" pmui-width="20px" pmui-label="" @pmui-click=${({ detail: e }) => {
|
||||
if (!!window.showDirectoryPicker && !e.shiftKey) {
|
||||
openWorldDirectory()
|
||||
} else {
|
||||
openFilePicker()
|
||||
}
|
||||
}}></pmui-button>` : ''}
|
||||
</div>
|
||||
<pmui-button pmui-width="200px" pmui-label="Options" @pmui-click=${() => showModal(document.getElementById('options-screen'))}></pmui-button>
|
||||
<div class="menu-row">
|
||||
<pmui-button pmui-width="98px" pmui-label="GitHub" @pmui-click=${() => openURL(process.env.GITHUB_URL)}></pmui-button>
|
||||
<pmui-button pmui-width="98px" pmui-label="Discord" @pmui-click=${() => openURL('https://discord.gg/4Ucm684Fq3')}></pmui-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bottom-info">
|
||||
<span title="${this.versionTitle} (click to reload)" @click=${this.reload}>Prismarine Web Client ${this.versionStatus}</span>
|
||||
<span>A Minecraft client in the browser!</span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-titlescreen', TitleScreen)
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
// todo implement async options storage
|
||||
|
||||
import { proxy, subscribe } from 'valtio/vanilla'
|
||||
// weird webpack configuration bug: it cant import valtio/utils in this file
|
||||
import { subscribeKey } from 'valtio/utils'
|
||||
|
||||
const mergeAny: <T>(arg1: T, arg2: any) => T = Object.assign
|
||||
|
||||
const defaultOptions = {
|
||||
renderDistance: 4,
|
||||
closeConfirmation: true,
|
||||
autoFullScreen: false,
|
||||
mouseRawInput: false,
|
||||
autoExitFullscreen: false,
|
||||
localUsername: 'wanderer',
|
||||
mouseSensX: 50,
|
||||
mouseSensY: 50 as number | true,
|
||||
// mouseInvertX: false,
|
||||
chatWidth: 320,
|
||||
chatHeight: 180,
|
||||
chatScale: 100,
|
||||
volume: 50,
|
||||
// fov: 70,
|
||||
fov: 75,
|
||||
guiScale: 3,
|
||||
autoRequestCompletions: true,
|
||||
touchButtonsSize: 40,
|
||||
highPerformanceGpu: false,
|
||||
|
||||
showChunkBorders: false,
|
||||
frameLimit: false as number | false,
|
||||
alwaysBackupWorldBeforeLoading: undefined as boolean | undefined | null,
|
||||
alwaysShowMobileControls: false,
|
||||
maxMultiplayerRenderDistance: 6,
|
||||
excludeCommunicationDebugEvents: [],
|
||||
preventDevReloadWhilePlaying: false,
|
||||
numWorkers: 4,
|
||||
localServerOptions: {},
|
||||
preferLoadReadonly: false,
|
||||
disableLoadPrompts: false,
|
||||
guestUsername: 'guest',
|
||||
askGuestName: true,
|
||||
|
||||
// advanced bot options
|
||||
autoRespawn: false
|
||||
}
|
||||
|
||||
export type AppOptions = typeof defaultOptions
|
||||
|
||||
export const options = proxy(
|
||||
mergeAny(defaultOptions, JSON.parse(localStorage.options || '{}'))
|
||||
)
|
||||
|
||||
window.options = window.settings = options
|
||||
|
||||
subscribe(options, () => {
|
||||
localStorage.options = JSON.stringify(options)
|
||||
})
|
||||
|
||||
type WatchValue = <T extends Record<string, any>>(proxy: T, callback: (p: T) => void) => void
|
||||
|
||||
export const watchValue: WatchValue = (proxy, callback) => {
|
||||
const watchedProps = new Set<string>()
|
||||
callback(new Proxy(proxy, {
|
||||
get (target, p, receiver) {
|
||||
watchedProps.add(p.toString())
|
||||
return Reflect.get(target, p, receiver)
|
||||
},
|
||||
}))
|
||||
for (const prop of watchedProps) {
|
||||
subscribeKey(proxy, prop, () => {
|
||||
callback(proxy)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watchValue(options, o => {
|
||||
globalThis.excludeCommunicationDebugEvents = o.excludeCommunicationDebugEvents
|
||||
})
|
||||
|
||||
export const useOptionValue = (setting, valueCallback) => {
|
||||
valueCallback(setting)
|
||||
subscribe(setting, valueCallback)
|
||||
}
|
||||
208
src/optionsStorage.tsx
Normal file
208
src/optionsStorage.tsx
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
// todo implement async options storage
|
||||
|
||||
import { proxy, subscribe } from 'valtio/vanilla'
|
||||
// weird webpack configuration bug: it cant import valtio/utils in this file
|
||||
import { subscribeKey } from 'valtio/utils'
|
||||
import { useState } from 'react'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { OptionMeta, OptionSlider } from './react/OptionsItems'
|
||||
import Button from './react/Button'
|
||||
import { openOptionsMenu } from './globalState'
|
||||
import { openURL } from './menus/components/common'
|
||||
import Slider from './react/Slider'
|
||||
import { getScreenRefreshRate } from './utils'
|
||||
|
||||
const mergeAny: <T>(arg1: T, arg2: any) => T = Object.assign
|
||||
|
||||
const defaultOptions = {
|
||||
renderDistance: 4,
|
||||
closeConfirmation: true,
|
||||
autoFullScreen: false,
|
||||
mouseRawInput: false,
|
||||
autoExitFullscreen: false,
|
||||
localUsername: 'wanderer',
|
||||
mouseSensX: 50,
|
||||
mouseSensY: 50 as number | true,
|
||||
// mouseInvertX: false,
|
||||
chatWidth: 320,
|
||||
chatHeight: 180,
|
||||
chatScale: 100,
|
||||
volume: 50,
|
||||
// fov: 70,
|
||||
fov: 75,
|
||||
guiScale: 3,
|
||||
autoRequestCompletions: true,
|
||||
touchButtonsSize: 40,
|
||||
highPerformanceGpu: false,
|
||||
|
||||
showChunkBorders: false,
|
||||
frameLimit: false as number | false,
|
||||
alwaysBackupWorldBeforeLoading: undefined as boolean | undefined | null,
|
||||
alwaysShowMobileControls: false,
|
||||
maxMultiplayerRenderDistance: null as number | null,
|
||||
excludeCommunicationDebugEvents: [],
|
||||
preventDevReloadWhilePlaying: false,
|
||||
numWorkers: 4,
|
||||
localServerOptions: {} as any,
|
||||
preferLoadReadonly: false,
|
||||
disableLoadPrompts: false,
|
||||
guestUsername: 'guest',
|
||||
askGuestName: true,
|
||||
|
||||
// advanced bot options
|
||||
autoRespawn: false
|
||||
}
|
||||
|
||||
export type AppOptions = typeof defaultOptions
|
||||
|
||||
export type OptionsGroupType = 'main' | 'render' | 'interface' | 'controls' | 'sound' | 'advanced'
|
||||
// todo refactor to separate file like optionsGUI.tsx
|
||||
export const guiOptionsScheme: {
|
||||
[t in OptionsGroupType]: Array<{ [k in keyof AppOptions]?: Partial<OptionMeta> } & { custom?}>
|
||||
} = {
|
||||
render: [
|
||||
{
|
||||
renderDistance: {
|
||||
unit: '',
|
||||
min: 2,
|
||||
max: 16
|
||||
},
|
||||
},
|
||||
{
|
||||
custom () {
|
||||
const frameLimitValue = useSnapshot(options).frameLimit
|
||||
const [frameLimitMax, setFrameLimitMax] = useState(null as number | null)
|
||||
|
||||
return <div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Slider style={{ width: 130 }} label='Frame Limit' disabledReason={frameLimitMax ? undefined : 'press lock button first'} unit={frameLimitValue ? 'fps' : ''} valueDisplay={frameLimitValue || 'VSync'} value={frameLimitValue || frameLimitMax + 1} min={20} max={frameLimitMax + 1} updateValue={(newVal) => {
|
||||
options.frameLimit = newVal > frameLimitMax ? false : newVal
|
||||
}} />
|
||||
<Button style={{ width: 20 }} icon='pixelarticons:lock-open' onClick={async () => {
|
||||
const rate = await getScreenRefreshRate()
|
||||
setFrameLimitMax(rate)
|
||||
}} />
|
||||
</div>
|
||||
}
|
||||
},
|
||||
{
|
||||
highPerformanceGpu: {
|
||||
text: 'Use Dedicated GPU',
|
||||
// willHaveNoEffect: isIos
|
||||
},
|
||||
},
|
||||
{
|
||||
custom () {
|
||||
return <Button label='Guide: Disable VSync' onClick={() => openURL('https://gist.github.com/zardoy/6e5ce377d2b4c1e322e660973da069cd')} inScreen />
|
||||
},
|
||||
}
|
||||
],
|
||||
main: [
|
||||
// renderDistance
|
||||
{
|
||||
fov: {
|
||||
min: 30,
|
||||
max: 110,
|
||||
unit: '',
|
||||
}
|
||||
},
|
||||
{
|
||||
custom () {
|
||||
return <Button label='Interface...' onClick={() => openOptionsMenu('interface')} inScreen />
|
||||
},
|
||||
},
|
||||
{
|
||||
custom () {
|
||||
return <Button label='Render...' onClick={() => openOptionsMenu('render')} inScreen />
|
||||
},
|
||||
},
|
||||
{
|
||||
custom () {
|
||||
return <Button label='Sound...' onClick={() => openOptionsMenu('sound')} inScreen />
|
||||
},
|
||||
},
|
||||
{
|
||||
custom () {
|
||||
return <Button label='Controls...' onClick={() => openOptionsMenu('controls')} inScreen />
|
||||
},
|
||||
}
|
||||
],
|
||||
interface: [
|
||||
{
|
||||
guiScale: {
|
||||
max: 4,
|
||||
unit: '',
|
||||
delayApply: true,
|
||||
},
|
||||
chatWidth: {
|
||||
max: 320,
|
||||
unit: 'px',
|
||||
},
|
||||
chatHeight: {
|
||||
max: 180,
|
||||
unit: 'px',
|
||||
},
|
||||
}
|
||||
],
|
||||
controls: [
|
||||
{
|
||||
// keybindings
|
||||
mouseSensX: {},
|
||||
mouseSensY: {},
|
||||
mouseRawInput: {},
|
||||
alwaysShowMobileControls: {
|
||||
text: 'Always Mobile Controls',
|
||||
},
|
||||
autoFullScreen: {
|
||||
tooltip: 'Auto Fullscreen allows you to use Ctrl+W and Escape having to wait/click on screen again.',
|
||||
},
|
||||
autoExitFullscreen: {
|
||||
tooltip: 'Exit fullscreen on escape (pause menu open). But note you can always do it with F11.',
|
||||
},
|
||||
touchButtonsSize: {
|
||||
min: 40
|
||||
}
|
||||
}
|
||||
],
|
||||
sound: [
|
||||
{ volume: {} }
|
||||
],
|
||||
advanced: [
|
||||
|
||||
],
|
||||
}
|
||||
|
||||
export const options = proxy(
|
||||
mergeAny(defaultOptions, JSON.parse(localStorage.options || '{}'))
|
||||
)
|
||||
|
||||
window.options = window.settings = options
|
||||
|
||||
subscribe(options, () => {
|
||||
localStorage.options = JSON.stringify(options)
|
||||
})
|
||||
|
||||
type WatchValue = <T extends Record<string, any>>(proxy: T, callback: (p: T) => void) => void
|
||||
|
||||
export const watchValue: WatchValue = (proxy, callback) => {
|
||||
const watchedProps = new Set<string>()
|
||||
callback(new Proxy(proxy, {
|
||||
get (target, p, receiver) {
|
||||
watchedProps.add(p.toString())
|
||||
return Reflect.get(target, p, receiver)
|
||||
},
|
||||
}))
|
||||
for (const prop of watchedProps) {
|
||||
subscribeKey(proxy, prop, () => {
|
||||
callback(proxy)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
watchValue(options, o => {
|
||||
globalThis.excludeCommunicationDebugEvents = o.excludeCommunicationDebugEvents
|
||||
})
|
||||
|
||||
export const useOptionValue = (setting, valueCallback) => {
|
||||
valueCallback(setting)
|
||||
subscribe(setting, valueCallback)
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import { fromTexturePackPath, resourcePackState } from './texturePack'
|
|||
import { options } from './optionsStorage'
|
||||
|
||||
let panoramaCubeMap
|
||||
let shouldDisplayPanorama = false
|
||||
let panoramaUsesResourePack = false
|
||||
let viewer
|
||||
|
||||
|
|
@ -57,8 +58,7 @@ subscribeKey(resourcePackState, 'resourcePackInstalled', async () => {
|
|||
// Menu panorama background
|
||||
export async function addPanoramaCubeMap () {
|
||||
if (panoramaCubeMap) return
|
||||
// remove all existing object in the viewer.scene
|
||||
// viewer.scene.children = []
|
||||
shouldDisplayPanorama = true
|
||||
|
||||
let time = 0
|
||||
viewer.camera = new THREE.PerspectiveCamera(85, window.innerWidth / window.innerHeight, 0.05, 1000)
|
||||
|
|
@ -78,6 +78,8 @@ export async function addPanoramaCubeMap () {
|
|||
}))
|
||||
}
|
||||
|
||||
if (!shouldDisplayPanorama) return
|
||||
|
||||
const panoramaBox = new THREE.Mesh(panorGeo, panorMaterials)
|
||||
|
||||
panoramaBox.onBeforeRender = () => {
|
||||
|
|
@ -90,7 +92,8 @@ export async function addPanoramaCubeMap () {
|
|||
group.add(panoramaBox)
|
||||
|
||||
const Entity = require('prismarine-viewer/viewer/lib/entity/Entity')
|
||||
for (let i = 0; i < 42; i++) {
|
||||
// should be rewritten entirely
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const m = new Entity('1.16.4', 'squid').mesh
|
||||
m.position.set(Math.random() * 30 - 15, Math.random() * 20 - 10, Math.random() * 10 - 17)
|
||||
m.rotation.set(0, Math.PI + Math.random(), -Math.PI / 4, 'ZYX')
|
||||
|
|
@ -112,4 +115,5 @@ export function removePanorama () {
|
|||
viewer.camera.updateProjectionMatrix()
|
||||
viewer.scene.remove(panoramaCubeMap)
|
||||
panoramaCubeMap = null
|
||||
shouldDisplayPanorama = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { playSound } from '../menus/components/button'
|
||||
import { loadSound, playSound } from '../basicSounds'
|
||||
import buttonCss from './button.module.css'
|
||||
|
||||
// testing in storybook from deathscreen
|
||||
|
|
@ -7,15 +7,22 @@ interface Props extends React.ComponentProps<'button'> {
|
|||
label?: string
|
||||
icon?: string
|
||||
children?: React.ReactNode
|
||||
inScreen?: boolean
|
||||
}
|
||||
|
||||
export default ({ label, icon, children, ...args }: Props) => {
|
||||
void loadSound('button_click.mp3')
|
||||
|
||||
export default ({ label, icon, children, inScreen, ...args }: Props) => {
|
||||
const onClick = (e) => {
|
||||
void playSound('button_click.mp3')
|
||||
args.onClick(e)
|
||||
}
|
||||
if (inScreen) {
|
||||
args.style ??= {}
|
||||
args.style.width = 150
|
||||
}
|
||||
|
||||
return <button className={buttonCss.button} onClick={onClick} {...args}>
|
||||
return <button className={buttonCss.button} {...args} onClick={onClick}>
|
||||
{icon && <iconify-icon class={buttonCss.icon} icon={icon}></iconify-icon>}
|
||||
{label}
|
||||
{children}
|
||||
|
|
|
|||
104
src/react/MainMenu.tsx
Normal file
104
src/react/MainMenu.tsx
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import styles from './mainMenu.module.css'
|
||||
import Button from './Button'
|
||||
|
||||
type Action = (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||
|
||||
interface Props {
|
||||
connectToServerAction?: Action
|
||||
singleplayerAction?: Action
|
||||
optionsAction?: Action
|
||||
githubAction?: Action
|
||||
discordAction?: Action
|
||||
openFileAction?: Action
|
||||
}
|
||||
|
||||
const refreshApp = async () => {
|
||||
const registration = await navigator.serviceWorker.getRegistration()
|
||||
await registration.unregister()
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
export default ({ connectToServerAction, singleplayerAction, optionsAction, githubAction, discordAction, openFileAction }: Props) => {
|
||||
const [versionStatus, setVersionStatus] = useState('')
|
||||
const [versionTitle, setVersionTitle] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
setVersionStatus('(dev)')
|
||||
} else {
|
||||
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'})`)
|
||||
setVersionTitle(`Loaded: ${process.env.BUILD_VERSION}. Remote: ${contents}`)
|
||||
}, () => { })
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles['game-title']}>
|
||||
<div className={styles.minec}></div>
|
||||
<div className={styles.raft}></div>
|
||||
<div className={styles.edition}></div>
|
||||
<span className={styles.splash}>Prismarine is a beautiful block</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.menu}>
|
||||
<Button
|
||||
onClick={connectToServerAction}
|
||||
data-test-id='connect-screen-button'
|
||||
>
|
||||
Connect to server
|
||||
</Button>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Button
|
||||
style={{ width: 170 }}
|
||||
onClick={singleplayerAction}
|
||||
data-test-id='singleplayer-button'
|
||||
>
|
||||
Singleplayer
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
style={{ width: '20px' }}
|
||||
data-test-id='select-file-folder'
|
||||
icon='pixelarticons:folder'
|
||||
onClick={openFileAction}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onClick={optionsAction}
|
||||
>
|
||||
Options
|
||||
</Button>
|
||||
<div className={styles['menu-row']}>
|
||||
<Button
|
||||
style={{ width: '98px' }}
|
||||
onClick={githubAction}
|
||||
>
|
||||
GitHub
|
||||
</Button>
|
||||
<Button
|
||||
style={{ width: '98px' }}
|
||||
onClick={discordAction}
|
||||
>
|
||||
Discord
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles['bottom-info']}>
|
||||
<span
|
||||
title={`${versionTitle} (click to reload)`}
|
||||
onClick={refreshApp}
|
||||
>
|
||||
Prismarine Web Client {versionStatus}
|
||||
</span>
|
||||
<span>A Minecraft client in the browser!</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
42
src/react/MainMenuRenderApp.tsx
Normal file
42
src/react/MainMenuRenderApp.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import fs from 'fs'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { activeModalStack, miscUiState, openOptionsMenu, showModal } from '../globalState'
|
||||
import { openURL } from '../menus/components/common'
|
||||
import { fsState } from '../loadSave'
|
||||
import { options } from '../optionsStorage'
|
||||
import defaultLocalServerOptions from '../defaultLocalServerOptions'
|
||||
import { openFilePicker } from '../utils'
|
||||
import { openWorldDirectory } from '../browserfs'
|
||||
import MainMenu from './MainMenu'
|
||||
|
||||
export default () => {
|
||||
const haveModals = useSnapshot(activeModalStack).length
|
||||
const { gameLoaded } = useSnapshot(miscUiState)
|
||||
if (haveModals || gameLoaded) return
|
||||
|
||||
return <MainMenu
|
||||
connectToServerAction={() => showModal(document.getElementById('play-screen'))}
|
||||
singleplayerAction={() => {
|
||||
fsState.isReadonly = false
|
||||
fsState.syncFs = true
|
||||
fsState.inMemorySave = true
|
||||
const notFirstTime = fs.existsSync('./world/level.dat')
|
||||
if (notFirstTime && !options.localServerOptions.version) {
|
||||
options.localServerOptions.version = '1.16.1' // legacy version
|
||||
} else {
|
||||
options.localServerOptions.version ??= defaultLocalServerOptions.version
|
||||
}
|
||||
window.dispatchEvent(new window.CustomEvent('singleplayer', {}))
|
||||
}}
|
||||
githubAction={() => openURL(process.env.GITHUB_URL)}
|
||||
optionsAction={() => openOptionsMenu('main')}
|
||||
discordAction={() => openURL('https://discord.gg/4Ucm684Fq3')}
|
||||
openFileAction={e => {
|
||||
if (!!window.showDirectoryPicker && !e.shiftKey) {
|
||||
openWorldDirectory()
|
||||
} else {
|
||||
openFilePicker()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
18
src/react/OptionsGroup.stories.tsx
Normal file
18
src/react/OptionsGroup.stories.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
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 () { }
|
||||
},
|
||||
}
|
||||
38
src/react/OptionsGroup.tsx
Normal file
38
src/react/OptionsGroup.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { titleCase } from 'title-case'
|
||||
import { OptionsGroupType, guiOptionsScheme, options } from '../optionsStorage'
|
||||
import OptionsItems, { OptionMeta } from './OptionsItems'
|
||||
|
||||
const optionValueToType = (optionValue: any) => {
|
||||
if (typeof optionValue === 'boolean') return 'toggle'
|
||||
if (typeof optionValue === 'number') return 'slider'
|
||||
if (typeof optionValue === 'string') return 'element'
|
||||
}
|
||||
|
||||
const finalItemsScheme: Record<keyof typeof guiOptionsScheme, OptionMeta[]> = Object.fromEntries(Object.entries(guiOptionsScheme).map(([groupName, optionsArr]) => {
|
||||
return [groupName, optionsArr.flatMap((optionsObj) => {
|
||||
return Object.entries(optionsObj).map(([optionKey, metaMerge]) => {
|
||||
const optionValue = options[optionKey]
|
||||
|
||||
const type = optionValueToType(optionValue)
|
||||
const meta: OptionMeta = {
|
||||
id: optionKey === 'custom' ? undefined : optionKey,
|
||||
type,
|
||||
// todo I don't like the whole idea of custom. Why it is even here?
|
||||
...optionKey === 'custom' ? {
|
||||
type: 'element',
|
||||
render: metaMerge
|
||||
} : {
|
||||
...metaMerge,
|
||||
}
|
||||
}
|
||||
return meta
|
||||
})
|
||||
})]
|
||||
}))
|
||||
|
||||
export default ({ group, backButtonAction }: { group: OptionsGroupType, backButtonAction?}) => {
|
||||
const items = finalItemsScheme[group]
|
||||
|
||||
const title = group === 'main' ? 'Settings' : `${titleCase(group)} Settings`
|
||||
return <OptionsItems items={items} title={title} backButtonAction={backButtonAction} />
|
||||
}
|
||||
93
src/react/OptionsItems.tsx
Normal file
93
src/react/OptionsItems.tsx
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import { useSnapshot } from 'valtio'
|
||||
import { noCase } from 'change-case'
|
||||
import { titleCase } from 'title-case'
|
||||
import { useMemo } from 'react'
|
||||
import { options } from '../optionsStorage'
|
||||
import Button from './Button'
|
||||
import Slider from './Slider'
|
||||
import Screen from './Screen'
|
||||
|
||||
type GeneralItem = {
|
||||
id?: string
|
||||
text?: string,
|
||||
disabledReason?: string,
|
||||
tooltip?: string
|
||||
willHaveNoEffect?: boolean
|
||||
}
|
||||
|
||||
export type OptionMeta = GeneralItem & ({
|
||||
type: 'toggle',
|
||||
} | {
|
||||
type: 'slider'
|
||||
min?: number,
|
||||
max?: number,
|
||||
valueText?: (value: number) => string,
|
||||
unit?: string,
|
||||
delayApply?: boolean,
|
||||
} | {
|
||||
type: 'element'
|
||||
render: () => React.ReactNode,
|
||||
})
|
||||
|
||||
export const OptionButton = ({ item }: { item: Extract<OptionMeta, { type: 'toggle' }> }) => {
|
||||
const optionValue = useSnapshot(options)[item.id]
|
||||
|
||||
return <Button
|
||||
label={`${item.text}: ${optionValue ? 'ON' : 'OFF'}`}
|
||||
onClick={() => {
|
||||
options[item.id] = !options[item.id]
|
||||
}}
|
||||
title={item.disabledReason ? `${item.disabledReason} | ${item.tooltip}` : item.tooltip}
|
||||
disabled={!!item.disabledReason}
|
||||
style={{
|
||||
width: 150,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
export const OptionSlider = ({ item }: { item: Extract<OptionMeta, { type: 'slider' }> }) => {
|
||||
const optionValue = useSnapshot(options)[item.id]
|
||||
|
||||
const valueDisplay = useMemo(() => {
|
||||
if (item.valueText) return item.valueText(optionValue)
|
||||
return undefined // default display
|
||||
}, [optionValue])
|
||||
|
||||
return <Slider label={item.text} value={options[item.id]} min={item.min} max={item.max} updateValue={(value) => {
|
||||
options[item.id] = value
|
||||
}} unit={item.unit} valueDisplay={valueDisplay} updateOnDragEnd={item.delayApply} />
|
||||
}
|
||||
|
||||
const OptionElement = ({ item }: { item: Extract<OptionMeta, { type: 'element' }> }) => {
|
||||
return item.render()
|
||||
}
|
||||
|
||||
const RenderOption = ({ item }: { item: OptionMeta }) => {
|
||||
if (item.id) {
|
||||
item.text ??= titleCase(noCase(item.id))
|
||||
}
|
||||
|
||||
if (item.type === 'toggle') return <OptionButton item={item} />
|
||||
if (item.type === 'slider') return <OptionSlider item={item} />
|
||||
if (item.type === 'element') return <OptionElement item={item} />
|
||||
}
|
||||
|
||||
interface Props {
|
||||
readonly items: OptionMeta[]
|
||||
title: string
|
||||
backButtonAction?: () => void
|
||||
}
|
||||
|
||||
export default ({ items, title, backButtonAction }: Props) => {
|
||||
return <Screen
|
||||
title={title}
|
||||
>
|
||||
<div className='screen-items'>
|
||||
{items.map((element, i) => {
|
||||
// make sure its unique!
|
||||
return <RenderOption key={element.id ?? `${title}-${i}`} item={element} />
|
||||
})}
|
||||
</div>
|
||||
{backButtonAction && <Button onClick={() => backButtonAction()}>Back</Button>}
|
||||
</Screen>
|
||||
}
|
||||
12
src/react/OptionsRenderApp.tsx
Normal file
12
src/react/OptionsRenderApp.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { useSnapshot } from 'valtio'
|
||||
import { activeModalStack, hideCurrentModal } from '../globalState'
|
||||
import { OptionsGroupType } from '../optionsStorage'
|
||||
import OptionsGroup from './OptionsGroup'
|
||||
|
||||
export default () => {
|
||||
const { reactType } = useSnapshot(activeModalStack).at(-1) ?? {}
|
||||
if (!reactType?.startsWith('options-')) return
|
||||
const settingsGroup = reactType.slice('options-'.length) as OptionsGroupType
|
||||
|
||||
return <OptionsGroup group={settingsGroup} backButtonAction={hideCurrentModal} />
|
||||
}
|
||||
|
|
@ -6,7 +6,9 @@ import Button from './Button'
|
|||
const meta: Meta<typeof Screen> = {
|
||||
component: Screen,
|
||||
render: () => <Screen title='test'>
|
||||
{Array.from({ length: 10 }).map((_, i) => <Button key={i}>test {i}</Button>)}
|
||||
<div className="screen-items">
|
||||
{Array.from({ length: 10 }).map((_, i) => <Button key={i} inScreen>test {i}</Button>)}
|
||||
</div>
|
||||
</Screen>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
interface Props {
|
||||
title: string
|
||||
children: React.ReactNode
|
||||
backdrop?: boolean
|
||||
}
|
||||
|
||||
export default ({ title, children }: Props) => {
|
||||
export default ({ title, children, backdrop = true }: Props) => {
|
||||
return (
|
||||
<div className='fullscreen' style={{ overflow: 'auto' }}>
|
||||
<div className="screen-content">
|
||||
<div className="screen-title">{title}</div>
|
||||
{children}
|
||||
<>
|
||||
{backdrop && <div className="backdrop"></div>}
|
||||
<div className='fullscreen' style={{ overflow: 'auto' }}>
|
||||
<div className="screen-content">
|
||||
<div className="screen-title">{title}</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
30
src/react/Slider.stories.tsx
Normal file
30
src/react/Slider.stories.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import Slider from './Slider'
|
||||
|
||||
const meta: Meta<typeof Slider> = {
|
||||
component: Slider,
|
||||
args: {
|
||||
label: 'hapiness',
|
||||
value: 0,
|
||||
updateValue (value) {
|
||||
console.log('updateValue', value)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof Slider>;
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
updateOnDragEnd: true,
|
||||
disabledReason: undefined,
|
||||
},
|
||||
}
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
disabledReason: 'you are not happy enough',
|
||||
},
|
||||
}
|
||||
83
src/react/Slider.tsx
Normal file
83
src/react/Slider.tsx
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// Slider.tsx
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import styles from './slider.module.css'
|
||||
|
||||
interface Props extends React.ComponentProps<'div'> {
|
||||
label: string;
|
||||
value: number;
|
||||
unit?: string;
|
||||
width?: number;
|
||||
valueDisplay?: string | number;
|
||||
min?: number;
|
||||
max?: number;
|
||||
disabledReason?: string;
|
||||
|
||||
updateValue?: (value: number) => void;
|
||||
updateOnDragEnd?: boolean;
|
||||
}
|
||||
|
||||
const Slider: React.FC<Props> = ({
|
||||
label,
|
||||
unit = '%',
|
||||
width,
|
||||
value: valueProp,
|
||||
valueDisplay,
|
||||
min = 1,
|
||||
max = 100,
|
||||
disabledReason,
|
||||
|
||||
updateOnDragEnd = false,
|
||||
updateValue,
|
||||
...divProps
|
||||
}) => {
|
||||
const [value, setValue] = useState(valueProp)
|
||||
const getRatio = (v = value) => Math.max(Math.min((v - min) / (max - min), 1), 0)
|
||||
const [ratio, setRatio] = useState(getRatio())
|
||||
|
||||
useEffect(() => {
|
||||
setValue(valueProp)
|
||||
}, [valueProp])
|
||||
useEffect(() => {
|
||||
setRatio(getRatio())
|
||||
}, [value, min, max])
|
||||
|
||||
const fireValueUpdate = (dragEnd: boolean, v = value) => {
|
||||
if (updateOnDragEnd !== dragEnd) return
|
||||
updateValue?.(v)
|
||||
}
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
export default Slider
|
||||
88
src/react/mainMenu.module.css
Normal file
88
src/react/mainMenu.module.css
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
.game-title {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: calc(50% - 137px);
|
||||
}
|
||||
|
||||
.game-title .minec {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-image: var(--title-gui);
|
||||
background-size: 256px;
|
||||
width: 155px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.game-title .raft {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 155px;
|
||||
background-image: var(--title-gui);
|
||||
background-size: 256px;
|
||||
width: 155px;
|
||||
height: 44px;
|
||||
background-position-y: -45px;
|
||||
}
|
||||
|
||||
.game-title .edition {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 37px;
|
||||
left: calc(88px + 5px);
|
||||
background-image: url('extra-textures/edition.png');
|
||||
background-size: 128px;
|
||||
width: 88px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.splash {
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
left: 227px;
|
||||
color: #ff0;
|
||||
transform: translate(-50%, -50%) rotateZ(-20deg) scale(1);
|
||||
width: max-content;
|
||||
text-shadow: 1px 1px #220;
|
||||
font-size: 10px;
|
||||
animation: splashAnim 400ms infinite alternate linear;
|
||||
}
|
||||
|
||||
@keyframes splashAnim {
|
||||
to {
|
||||
transform: translate(-50%, -50%) rotateZ(-20deg) scale(1.07);
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px 0;
|
||||
position: absolute;
|
||||
top: calc(25% + 48px);
|
||||
left: 50%;
|
||||
width: 200px;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
|
||||
.menu-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0 4px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bottom-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 1px;
|
||||
width: calc(100% - 2px);
|
||||
color: white;
|
||||
text-shadow: 1px 1px #222;
|
||||
font-size: 10px;
|
||||
}
|
||||
9
src/react/options.module.css
Normal file
9
src/react/options.module.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
gap: 0 10px;
|
||||
height: 20px;
|
||||
}
|
||||
115
src/react/slider.module.css
Normal file
115
src/react/slider.module.css
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
.slider-container {
|
||||
--txrV: -46px;
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 20px;
|
||||
font-family: minecraft, mojangles, monospace;
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
text-shadow: 1px 1px #220;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.slider-thumb {
|
||||
--txrV: -66px;
|
||||
pointer-events: none;
|
||||
width: 8px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.slider-container:hover .slider:not(:disabled)~.slider-thumb {
|
||||
--txrV: -86px;
|
||||
}
|
||||
|
||||
.slider-container::after,
|
||||
.slider-thumb::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 50%;
|
||||
height: 20px;
|
||||
background: var(--widgets-gui-atlas);
|
||||
background-size: 256px;
|
||||
background-position-y: var(--txrV);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.slider-container::before,
|
||||
.slider-thumb::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 50%;
|
||||
height: 20px;
|
||||
background: var(--widgets-gui-atlas);
|
||||
background-size: 256px;
|
||||
background-position-x: calc(-200px + 100%);
|
||||
background-position-y: var(--txrV);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.slider {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: none;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.slider:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.slider~.disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* .disabled after .slider selector */
|
||||
.slider:disabled~.disabled {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
background: rgba(0, 0, 0, .5);
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
position: relative;
|
||||
appearance: none;
|
||||
width: 8px;
|
||||
height: 20px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.slider::-moz-range-thumb {
|
||||
width: 8px;
|
||||
height: 20px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.label {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 6;
|
||||
width: max-content;
|
||||
}
|
||||
|
|
@ -7,9 +7,11 @@ import { useSnapshot } from 'valtio'
|
|||
import { QRCodeSVG } from 'qrcode.react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { contro } from './controls'
|
||||
import { activeModalStack, isGameActive, miscUiState } from './globalState'
|
||||
import { miscUiState } from './globalState'
|
||||
import { options, watchValue } from './optionsStorage'
|
||||
import DeathScreenProvider from './react/DeathScreenProvider'
|
||||
import OptionsRenderApp from './react/OptionsRenderApp'
|
||||
import MainMenuRenderApp from './react/MainMenuRenderApp'
|
||||
|
||||
// todo
|
||||
useInterfaceState.setState({
|
||||
|
|
@ -77,12 +79,6 @@ const TouchControls = () => {
|
|||
)
|
||||
}
|
||||
|
||||
function useIsBotAvailable () {
|
||||
const stack = useSnapshot(activeModalStack)
|
||||
|
||||
return isGameActive(false)
|
||||
}
|
||||
|
||||
const Portal = ({ children, to }) => {
|
||||
return createPortal(children, to)
|
||||
}
|
||||
|
|
@ -112,17 +108,27 @@ const DisplayQr = () => {
|
|||
</div>
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const isBotAvailable = useIsBotAvailable()
|
||||
if (!isBotAvailable) return null
|
||||
const InGameUi = () => {
|
||||
const { gameLoaded } = useSnapshot(miscUiState)
|
||||
if (!gameLoaded) return
|
||||
|
||||
return <div>
|
||||
return <>
|
||||
<Portal to={document.querySelector('#ui-root')}>
|
||||
{/* apply scaling */}
|
||||
<DeathScreenProvider />
|
||||
</Portal>
|
||||
<DisplayQr />
|
||||
<TouchControls />
|
||||
</>
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
return <div>
|
||||
<InGameUi />
|
||||
<Portal to={document.querySelector('#ui-root')}>
|
||||
<OptionsRenderApp />
|
||||
<MainMenuRenderApp />
|
||||
</Portal>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
|||
43
src/screens.css
Normal file
43
src/screens.css
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* screen styles eg main menu and options are screens */
|
||||
|
||||
.title, .screen-title {
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-shadow: 1px 1px #222;
|
||||
}
|
||||
|
||||
.screen-title {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
.fullscreen {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.screen-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 10px;
|
||||
/* todo I'm not sure about it */
|
||||
/* margin-top: calc(100% / 6 - 16px); */
|
||||
width: 310px;
|
||||
align-items: center;
|
||||
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.screen-items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 10px;
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
@import url(./screens.css);
|
||||
|
||||
:root {
|
||||
--guiScaleFactor: 3;
|
||||
--guiScale: 3;
|
||||
|
|
@ -21,6 +23,7 @@ html {
|
|||
height: 100vh;
|
||||
overflow: hidden;
|
||||
--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');
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +124,7 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 561px) {
|
||||
@media only screen and (max-width: 590px) {
|
||||
#ui-root {
|
||||
transform: scale(1);
|
||||
width: calc(100% / 1);
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import type { Viewer } from 'prismarine-viewer/viewer/lib/viewer'
|
|||
import { subscribeKey } from 'valtio/utils'
|
||||
import { proxy, ref } from 'valtio'
|
||||
import blocksFileNames from '../generated/blocks.json'
|
||||
import { showNotification } from './menus/notification'
|
||||
import type { BlockStates } from './inventory'
|
||||
import { removeFileRecursiveAsync } from './browserfs'
|
||||
import { setLoadingScreenStatus } from './utils'
|
||||
import { showNotification } from './globalState'
|
||||
|
||||
export const resourcePackState = proxy({
|
||||
resourcePackInstalled: false,
|
||||
|
|
|
|||
23
src/utils.ts
23
src/utils.ts
|
|
@ -1,7 +1,4 @@
|
|||
import * as crypto from 'crypto'
|
||||
import UUID from 'uuid-1345'
|
||||
import { activeModalStack, hideModal, miscUiState, showModal } from './globalState'
|
||||
import { notification } from './menus/notification'
|
||||
import { activeModalStack, hideModal, miscUiState, notification, showModal } from './globalState'
|
||||
import { options } from './optionsStorage'
|
||||
import { saveWorld } from './builtinCommands'
|
||||
import { openWorldZip } from './browserfs'
|
||||
|
|
@ -118,20 +115,6 @@ export const isCypress = () => {
|
|||
return localStorage.cypress === 'true'
|
||||
}
|
||||
|
||||
// https://github.com/PrismarineJS/node-minecraft-protocol/blob/cf1f67117d586b5e6e21f0d9602da12e9fcf46b6/src/server/login.js#L170
|
||||
function javaUUID (s: string) {
|
||||
const hash = crypto.createHash('md5')
|
||||
hash.update(s, 'utf8')
|
||||
const buffer = hash.digest()
|
||||
buffer[6] = (buffer[6] & 0x0f) | 0x30
|
||||
buffer[8] = (buffer[8] & 0x3f) | 0x80
|
||||
return buffer
|
||||
}
|
||||
|
||||
export function nameToMcOfflineUUID (name) {
|
||||
return (new UUID(javaUUID('OfflinePlayer:' + name))).toString()
|
||||
}
|
||||
|
||||
export const setLoadingScreenStatus = function (status: string | undefined, isError = false, hideDots = false) {
|
||||
const loadingScreen = document.getElementById('loading-error-screen')
|
||||
|
||||
|
|
@ -179,10 +162,10 @@ export const setRenderDistance = () => {
|
|||
}
|
||||
prevRenderDistance = options.renderDistance
|
||||
}
|
||||
export const reloadChunks = () => {
|
||||
export const reloadChunks = async () => {
|
||||
if (!worldView) return
|
||||
setRenderDistance()
|
||||
worldView.updatePosition(bot.entity.position, true)
|
||||
await worldView.updatePosition(bot.entity.position, true)
|
||||
}
|
||||
|
||||
export const openFilePicker = (specificCase?: 'resourcepack') => {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,13 @@ import { reloadChunks } from './utils'
|
|||
|
||||
subscribeKey(options, 'renderDistance', reloadChunks)
|
||||
|
||||
watchValue(options, o => {
|
||||
document.documentElement.style.setProperty('--chatScale', `${o.chatScale / 100}`)
|
||||
document.documentElement.style.setProperty('--chatWidth', `${o.chatWidth}px`)
|
||||
document.documentElement.style.setProperty('--chatHeight', `${o.chatHeight}px`)
|
||||
document.documentElement.style.setProperty('--guiScale', `${o.guiScale}`)
|
||||
})
|
||||
|
||||
export const watchOptionsAfterViewerInit = () => {
|
||||
watchValue(options, o => {
|
||||
viewer.world.showChunkBorders = o.showChunkBorders
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"jsx": "react-jsx",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true,
|
||||
"resolveJsonModule": false,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitAny": false,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
},
|
||||
"include": [
|
||||
"src",
|
||||
"cypress"
|
||||
"cypress",
|
||||
"prismarine-viewer/viewer"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue