add storybook & react button component, refactor styles a bit
fix death screen was on top of other screens
This commit is contained in:
parent
ad7d53c595
commit
d77484c966
16 changed files with 4019 additions and 430 deletions
|
|
@ -7,7 +7,7 @@
|
|||
"rules": {
|
||||
"space-infix-ops": "error",
|
||||
"no-multi-spaces": "error",
|
||||
"space-after-function-name": "error",
|
||||
"space-before-function-paren": "error",
|
||||
"space-in-parens": [
|
||||
"error",
|
||||
"never"
|
||||
|
|
|
|||
14
.storybook/main.ts
Normal file
14
.storybook/main.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import type { StorybookConfig } from "@storybook/react-vite";
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
|
||||
framework: {
|
||||
name: "@storybook/react-vite",
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: "tag",
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
27
.storybook/preview.tsx
Normal file
27
.storybook/preview.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react'
|
||||
|
||||
import type { Preview } from "@storybook/react";
|
||||
|
||||
import '../src/styles.css'
|
||||
import './storybook.css'
|
||||
|
||||
const preview: Preview = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div id='ui-root'>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
parameters: {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
18
.storybook/storybook.css
Normal file
18
.storybook/storybook.css
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#storybook-root::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-image: url("../assets/storybook-bg.jpg");
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: minecraft;
|
||||
src: url(../assets/minecraftia.woff);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: mojangles;
|
||||
src: url(../assets/mojangles.ttf);
|
||||
}
|
||||
12
package.json
12
package.json
|
|
@ -12,7 +12,9 @@
|
|||
"prod-start": "node server.js",
|
||||
"postinstall": "node scripts/gen-texturepack-files.mjs",
|
||||
"test-mc-server": "tsx cypress/minecraft-server.mjs",
|
||||
"lint": "eslint \"{src,cypress}/**/*.{ts,js,jsx,tsx}\""
|
||||
"lint": "eslint \"{src,cypress}/**/*.{ts,js,jsx,tsx}\"",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"keywords": [
|
||||
"prismarine",
|
||||
|
|
@ -57,6 +59,13 @@
|
|||
"workbox-build": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-essentials": "^7.4.6",
|
||||
"@storybook/addon-links": "^7.4.6",
|
||||
"@storybook/blocks": "^7.4.6",
|
||||
"@storybook/react": "^7.4.6",
|
||||
"@storybook/react-vite": "^7.4.6",
|
||||
"@storybook/web-components": "^7.4.6",
|
||||
"@storybook/web-components-vite": "^7.4.6",
|
||||
"@types/lodash-es": "^4.17.9",
|
||||
"@types/stats.js": "^0.17.1",
|
||||
"@types/three": "0.128.0",
|
||||
|
|
@ -86,6 +95,7 @@
|
|||
"prismarine-viewer": "link:prismarine-viewer",
|
||||
"process": "github:PrismarineJS/node-process",
|
||||
"rimraf": "^5.0.1",
|
||||
"storybook": "^7.4.6",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"three": "0.128.0",
|
||||
"timers-browserify": "^2.0.12",
|
||||
|
|
|
|||
3852
pnpm-lock.yaml
generated
3852
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
18
src/globals.d.ts
vendored
18
src/globals.d.ts
vendored
|
|
@ -9,7 +9,7 @@ declare const worldView: import('prismarine-viewer/viewer/lib/worldDataEmitter')
|
|||
declare const localServer: any
|
||||
|
||||
declare interface Document {
|
||||
getElementById(id): any
|
||||
getElementById (id): any
|
||||
exitPointerLock?(): void
|
||||
}
|
||||
|
||||
|
|
@ -20,8 +20,8 @@ declare namespace JSX {
|
|||
}
|
||||
|
||||
declare interface DocumentFragment {
|
||||
getElementById(id): HTMLElement & Record<string, any>
|
||||
querySelector(id): HTMLElement & Record<string, any>
|
||||
getElementById (id): HTMLElement & Record<string, any>
|
||||
querySelector (id): HTMLElement & Record<string, any>
|
||||
}
|
||||
|
||||
declare interface Window extends Record<string, any> {
|
||||
|
|
@ -32,13 +32,17 @@ type StringKeys<T extends object> = Extract<keyof T, string>
|
|||
|
||||
|
||||
interface ObjectConstructor {
|
||||
keys<T extends object>(obj: T): Array<StringKeys<T>>
|
||||
entries<T extends object>(obj: T): Array<[StringKeys<T>, T[keyof T]]>
|
||||
keys<T extends object> (obj: T): Array<StringKeys<T>>
|
||||
entries<T extends object> (obj: T): Array<[StringKeys<T>, T[keyof T]]>
|
||||
// todo review https://stackoverflow.com/questions/57390305/trying-to-get-fromentries-type-right
|
||||
fromEntries<T extends Array<[string, any]>>(obj: T): Record<T[number][0], T[number][1]>
|
||||
assign<T extends Record<string, any>, K extends Record<string, any>>(target: T, source: K): asserts target is T & K
|
||||
fromEntries<T extends Array<[string, any]>> (obj: T): Record<T[number][0], T[number][1]>
|
||||
assign<T extends Record<string, any>, K extends Record<string, any>> (target: T, source: K): asserts target is T & K
|
||||
}
|
||||
|
||||
declare module '*.module.css' {
|
||||
const css: Record<string, string>
|
||||
export default css
|
||||
}
|
||||
declare module '*.css' {
|
||||
const css: string
|
||||
export default css
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
//@ts-check
|
||||
const { LitElement, html, css, unsafeCSS } = require('lit')
|
||||
const widgetsGui = require('minecraft-assets/minecraft-assets/data/1.17.1/gui/widgets.png')
|
||||
const { options } = require('../../optionsStorage')
|
||||
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>} */
|
||||
|
|
|
|||
21
src/react/Button.tsx
Normal file
21
src/react/Button.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { playSound } from '../menus/components/button'
|
||||
import buttonCss from './button.module.css'
|
||||
|
||||
// testing in storybook from deathscreen
|
||||
|
||||
interface Props extends React.ComponentProps<'button'> {
|
||||
label: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
export default ({ label, icon, ...args }: Props) => {
|
||||
const onClick = (e) => {
|
||||
void playSound('button_click.mp3')
|
||||
args.onClick(e)
|
||||
}
|
||||
|
||||
return <button className={buttonCss.button} onClick={onClick} {...args}>
|
||||
{icon && <iconify-icon class={buttonCss.icon} icon={icon}></iconify-icon>}
|
||||
{label}
|
||||
</button>
|
||||
}
|
||||
24
src/react/DeathScreen.stories.tsx
Normal file
24
src/react/DeathScreen.stories.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import DeathScreen from './DeathScreen'
|
||||
|
||||
const meta: Meta<typeof DeathScreen> = {
|
||||
component: DeathScreen,
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof DeathScreen>;
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
dieReasonMessage: [
|
||||
{
|
||||
text: 'test',
|
||||
}
|
||||
],
|
||||
respawnCallback () {
|
||||
},
|
||||
disconnectCallback () {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -1,59 +1,15 @@
|
|||
import { useEffect } from 'react'
|
||||
import './deathScreen.css'
|
||||
import { proxy, useSnapshot } from 'valtio'
|
||||
import { disconnect } from '../utils'
|
||||
import { MessageFormatPart, formatMessage } from '../botUtils'
|
||||
import { options } from '../optionsStorage'
|
||||
import { hideModal, showModal } from '../globalState'
|
||||
import type { MessageFormatPart } from '../botUtils'
|
||||
import MessageFormatted from './MessageFormatted'
|
||||
import Button from './Button'
|
||||
|
||||
const dieReasonProxy = proxy({ value: null as MessageFormatPart[] | null })
|
||||
|
||||
export default () => {
|
||||
const { value: dieReasonMessage } = useSnapshot(dieReasonProxy)
|
||||
|
||||
useEffect(() => {
|
||||
type DeathEvent = {
|
||||
playerId: number
|
||||
entityId: number
|
||||
message: string
|
||||
}
|
||||
|
||||
bot._client.on('death_combat_event', (data: DeathEvent) => {
|
||||
try {
|
||||
if (data.playerId !== bot.entity.id) return
|
||||
const messageParsed = JSON.parse(data.message)
|
||||
const parts = formatMessage(messageParsed)
|
||||
dieReasonProxy.value = parts
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
bot.on('death', () => {
|
||||
if (dieReasonProxy.value) return
|
||||
dieReasonProxy.value = []
|
||||
})
|
||||
|
||||
bot.on('respawn', () => {
|
||||
// todo don't close too early, instead wait for health event and make button disabled?
|
||||
dieReasonProxy.value = null
|
||||
})
|
||||
|
||||
if (bot.health === 0) {
|
||||
dieReasonProxy.value = []
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (dieReasonProxy.value) {
|
||||
showModal({ reactType: 'death-screen' })
|
||||
} else {
|
||||
hideModal({ reactType: 'death-screen' })
|
||||
}
|
||||
}, [dieReasonMessage])
|
||||
|
||||
if (!dieReasonMessage || options.autoRespawn) return null
|
||||
type Props = {
|
||||
dieReasonMessage: readonly MessageFormatPart[]
|
||||
respawnCallback: () => void
|
||||
disconnectCallback: () => void
|
||||
}
|
||||
|
||||
export default ({ dieReasonMessage, respawnCallback, disconnectCallback }: Props) => {
|
||||
return (
|
||||
<div className='deathScreen-container'>
|
||||
<div className="deathScreen">
|
||||
|
|
@ -62,12 +18,11 @@ export default () => {
|
|||
<MessageFormatted parts={dieReasonMessage} />
|
||||
</h5>
|
||||
<div className='deathScreen-buttons-grouped'>
|
||||
<pmui-button pmui-label="Respawn" onClick={() => {
|
||||
console.log('respawn')
|
||||
bot._client.write('client_command', bot.supportFeature('respawnIsPayload') ? { payload: 0 } : { actionId: 0 })
|
||||
<Button label="Respawn" onClick={() => {
|
||||
respawnCallback()
|
||||
}} />
|
||||
<pmui-button pmui-label="Disconnnect" onClick={() => {
|
||||
disconnect()
|
||||
<Button label="Disconnnect" onClick={() => {
|
||||
disconnectCallback()
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
66
src/react/DeathScreenProvider.tsx
Normal file
66
src/react/DeathScreenProvider.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { useEffect } from 'react'
|
||||
import { proxy, useSnapshot } from 'valtio'
|
||||
import { disconnect } from '../utils'
|
||||
import { MessageFormatPart, formatMessage } from '../botUtils'
|
||||
import { showModal, hideModal, activeModalStack } from '../globalState'
|
||||
import { options } from '../optionsStorage'
|
||||
import DeathScreen from './DeathScreen'
|
||||
|
||||
const dieReasonProxy = proxy({ value: null as MessageFormatPart[] | null })
|
||||
|
||||
export default () => {
|
||||
const { value: dieReasonMessage } = useSnapshot(dieReasonProxy)
|
||||
const activeModals = useSnapshot(activeModalStack)
|
||||
|
||||
useEffect(() => {
|
||||
type DeathEvent = {
|
||||
playerId: number
|
||||
entityId: number
|
||||
message: string
|
||||
}
|
||||
|
||||
bot._client.on('death_combat_event', (data: DeathEvent) => {
|
||||
try {
|
||||
if (data.playerId !== bot.entity.id) return
|
||||
const messageParsed = JSON.parse(data.message)
|
||||
const parts = formatMessage(messageParsed)
|
||||
dieReasonProxy.value = parts
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
bot.on('death', () => {
|
||||
if (dieReasonProxy.value) return
|
||||
dieReasonProxy.value = []
|
||||
})
|
||||
|
||||
bot.on('respawn', () => {
|
||||
// todo don't close too early, instead wait for health event and make button disabled?
|
||||
dieReasonProxy.value = null
|
||||
})
|
||||
|
||||
if (bot.health === 0) {
|
||||
dieReasonProxy.value = []
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (dieReasonProxy.value) {
|
||||
showModal({ reactType: 'death-screen' })
|
||||
} else {
|
||||
hideModal({ reactType: 'death-screen' })
|
||||
}
|
||||
}, [dieReasonMessage])
|
||||
|
||||
if (!dieReasonMessage || options.autoRespawn || activeModals.length) return null
|
||||
|
||||
return <DeathScreen
|
||||
dieReasonMessage={dieReasonMessage}
|
||||
respawnCallback={() => {
|
||||
bot._client.write('client_command', bot.supportFeature('respawnIsPayload') ? { payload: 0 } : { actionId: 0 })
|
||||
}}
|
||||
disconnectCallback={() => {
|
||||
disconnect()
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
|
@ -38,13 +38,13 @@ export function getColorShadow (hex, dim = 0.25) {
|
|||
return `#${f(r)}${f(g)}${f(b)}`
|
||||
}
|
||||
|
||||
function parseInlineStyle(style: string): Record<string, any> {
|
||||
function parseInlineStyle (style: string): Record<string, any> {
|
||||
const template = document.createElement('template')
|
||||
template.setAttribute('style', style)
|
||||
return Object.fromEntries(Object.entries(template.style)
|
||||
.filter(([ key ]) => !/^\d+$/.test(key))
|
||||
.filter(([ , value ]) => Boolean(value))
|
||||
.map(([ key, value ]) => [key, value]))
|
||||
.filter(([key]) => !/^\d+$/.test(key))
|
||||
.filter(([, value]) => Boolean(value))
|
||||
.map(([key, value]) => [key, value]))
|
||||
}
|
||||
|
||||
export const messageFormatStylesMap = {
|
||||
|
|
|
|||
65
src/react/button.module.css
Normal file
65
src/react/button.module.css
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
.button {
|
||||
--txrV: 66px;
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 20px;
|
||||
font-family: minecraft, mojangles, monospace;
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
text-shadow: 1px 1px #222;
|
||||
border: none;
|
||||
z-index: 1;
|
||||
outline: none;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button:focus-visible {
|
||||
--txrV: 86px;
|
||||
}
|
||||
|
||||
.button:disabled {
|
||||
--txrV: 46px;
|
||||
color: #A0A0A0;
|
||||
text-shadow: 1px 1px #111;
|
||||
}
|
||||
|
||||
.button::before,
|
||||
.button::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 50%;
|
||||
height: 20px;
|
||||
background: var(--widgets-gui-atlas);
|
||||
background-size: 256px;
|
||||
background-position-y: calc(var(--txrV) * -1);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.button::before {
|
||||
left: 50%;
|
||||
background-position-x: calc(-200px + 100%);
|
||||
}
|
||||
|
||||
.button::after {
|
||||
left: 0;
|
||||
width: calc(50% + 1px);
|
||||
}
|
||||
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* @media (pointer: coarse) {
|
||||
.button {
|
||||
height: 30px;
|
||||
}
|
||||
} */
|
||||
|
|
@ -6,10 +6,10 @@ import { css } from '@emotion/css'
|
|||
import { useSnapshot } from 'valtio'
|
||||
import { QRCodeSVG } from 'qrcode.react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import DeathScreen from './react/DeathScreen'
|
||||
import { contro } from './controls'
|
||||
import { activeModalStack, isGameActive, miscUiState } from './globalState'
|
||||
import { options, watchValue } from './optionsStorage'
|
||||
import DeathScreenProvider from './react/DeathScreenProvider'
|
||||
|
||||
// todo
|
||||
useInterfaceState.setState({
|
||||
|
|
@ -17,7 +17,7 @@ useInterfaceState.setState({
|
|||
uiCustomization: {
|
||||
touchButtonSize: 40,
|
||||
},
|
||||
updateCoord([coord, state]) {
|
||||
updateCoord ([coord, state]) {
|
||||
const coordToAction = [
|
||||
['z', -1, 'KeyW'],
|
||||
['z', 1, 'KeyS'],
|
||||
|
|
@ -77,7 +77,7 @@ const TouchControls = () => {
|
|||
)
|
||||
}
|
||||
|
||||
function useIsBotAvailable() {
|
||||
function useIsBotAvailable () {
|
||||
const stack = useSnapshot(activeModalStack)
|
||||
|
||||
return isGameActive(false)
|
||||
|
|
@ -119,7 +119,7 @@ const App = () => {
|
|||
return <div>
|
||||
<Portal to={document.querySelector('#ui-root')}>
|
||||
{/* apply scaling */}
|
||||
<DeathScreen />
|
||||
<DeathScreenProvider />
|
||||
</Portal>
|
||||
<DisplayQr />
|
||||
<TouchControls />
|
||||
|
|
|
|||
|
|
@ -12,11 +12,6 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#react-root {
|
||||
z-index: 9;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
|
|
@ -25,6 +20,30 @@ html {
|
|||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
--widgets-gui-atlas: url('minecraft-assets/minecraft-assets/data/1.17.1/gui/widgets.png')
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin:0;
|
||||
padding:0;
|
||||
height: 100vh;
|
||||
/* font-family: sans-serif; */
|
||||
background: #333;
|
||||
/* background: linear-gradient(#141e30, #243b55); */
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
font-family: minecraft, mojangles, monospace;
|
||||
}
|
||||
|
||||
#react-root {
|
||||
z-index: 9;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
.dirt-bg {
|
||||
|
|
@ -51,34 +70,6 @@ html {
|
|||
src: url(mojangles.ttf);
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin:0;
|
||||
padding:0;
|
||||
height: 100vh;
|
||||
font-family: sans-serif;
|
||||
background: #333;
|
||||
/* background: linear-gradient(#141e30, #243b55); */
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#viewer-canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#ui-root {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
|
@ -94,7 +85,17 @@ body {
|
|||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
font-family: minecraft, mojangles, monospace;
|
||||
}
|
||||
|
||||
#viewer-canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-size: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 971px) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue