wip add new touch controls and other stuff
This commit is contained in:
parent
fbfbce5498
commit
333b2300e3
10 changed files with 215 additions and 4 deletions
17
src/gameUnload.ts
Normal file
17
src/gameUnload.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { subscribe } from 'valtio'
|
||||
import { miscUiState } from './globalState'
|
||||
|
||||
let toCleanup = [] as Array<() => void>
|
||||
|
||||
export const watchUnloadForCleanup = (func: () => void) => {
|
||||
toCleanup.push(func)
|
||||
}
|
||||
|
||||
subscribe(miscUiState, () => {
|
||||
if (!miscUiState.gameLoaded) {
|
||||
for (const func of toCleanup) {
|
||||
func()
|
||||
}
|
||||
toCleanup = []
|
||||
}
|
||||
})
|
||||
|
|
@ -95,6 +95,7 @@ type Story = StoryObj<typeof Chat>
|
|||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
usingTouch: false,
|
||||
messages: [{
|
||||
parts: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ const MessageLine = ({ message }: { message: Message }) => {
|
|||
|
||||
type Props = {
|
||||
messages: Message[]
|
||||
usingTouch: boolean
|
||||
opacity?: number
|
||||
opened?: boolean
|
||||
onClose?: () => void
|
||||
|
|
@ -52,9 +53,7 @@ export const fadeMessage = (message: Message, initialTimeout: boolean, requestUp
|
|||
}, initialTimeout ? 5000 : 0)
|
||||
}
|
||||
|
||||
export default ({ messages, opacity = 1, fetchCompletionItems, opened, sendMessage, onClose }: Props) => {
|
||||
const usingTouch = useSnapshot(miscUiState).currentTouch
|
||||
|
||||
export default ({ messages, opacity = 1, fetchCompletionItems, opened, sendMessage, onClose, usingTouch }: Props) => {
|
||||
const sendHistoryRef = useRef(JSON.parse(window.sessionStorage.chatHistory || '[]'))
|
||||
|
||||
const [completePadText, setCompletePadText] = useState('')
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { formatMessage } from '../botUtils'
|
||||
import { getBuiltinCommandsList, tryHandleBuiltinCommand } from '../builtinCommands'
|
||||
import { hideCurrentModal } from '../globalState'
|
||||
import { hideCurrentModal, miscUiState } from '../globalState'
|
||||
import { options } from '../optionsStorage'
|
||||
import ChatContainer, { Message, fadeMessage } from './ChatContainer'
|
||||
import { useIsModalActive } from './utils'
|
||||
|
|
@ -11,6 +12,7 @@ export default () => {
|
|||
const isChatActive = useIsModalActive('chat')
|
||||
const { messagesLimit, chatOpacity, chatOpacityOpened } = options
|
||||
const lastMessageId = useRef(0)
|
||||
const usingTouch = useSnapshot(miscUiState).currentTouch
|
||||
|
||||
useEffect(() => {
|
||||
bot.addListener('message', (jsonMsg, position) => {
|
||||
|
|
@ -33,6 +35,7 @@ export default () => {
|
|||
}, [])
|
||||
|
||||
return <ChatContainer
|
||||
usingTouch={!!usingTouch}
|
||||
opacity={(isChatActive ? chatOpacityOpened : chatOpacity) / 100}
|
||||
messages={messages}
|
||||
opened={isChatActive}
|
||||
|
|
|
|||
52
src/react/ConceptCommandsGui.stories.tsx
Normal file
52
src/react/ConceptCommandsGui.stories.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import Button from './Button'
|
||||
|
||||
const defaultIcon = <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M3 21H5H19H21V3H19H5H3V21ZM19 5V19H5V5H19ZM11 17H13V11H15V9H13V7H11V9H9V11H11V17ZM9 13V11H7V13H9ZM17 13H15V11H17V13Z" fill="currentColor"></path></svg>
|
||||
|
||||
const Button2 = ({ title, icon }) => {
|
||||
//@ts-expect-error
|
||||
return <Button style={{ '--scale': 4 }}>
|
||||
<div style={{ fontSize: '22px', fontWeight: 'bold', display: 'flex', gap: 3, flexDirection: 'column', alignItems: 'center' }}>
|
||||
<div>
|
||||
{title}
|
||||
</div>
|
||||
{/* <iconify-icon icon="pixelarticons: */}
|
||||
<div style={{ width: 30, height: 30 }} className='full-svg'>
|
||||
{icon}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
|
||||
const Comp = () => {
|
||||
return <div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(4, 1fr)',
|
||||
gap: 10
|
||||
}}>
|
||||
<Button2 title="/give" icon={defaultIcon} />
|
||||
<Button2 title="/tell" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M4 2h18v16H6v2H4v-2h2v-2h14V4H4v18H2V2h2zm5 7H7v2h2V9zm2 0h2v2h-2V9zm6 0h-2v2h2V9z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/setblock" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M2 2h20v20H2V2zm2 2v4h4V4H4zm6 0v4h4V4h-4zm6 0v4h4V4h-4zm4 6h-4v4h4v-4zm0 6h-4v4h4v-4zm-6 4v-4h-4v4h4zm-6 0v-4H4v4h4zm-4-6h4v-4H4v4zm6-4v4h4v-4h-4z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/tp" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M16 5H2v14h14v-2h2v-2h2v-2h2v-2h-2V9h-2V7h-2V5zm0 2v2h2v2h2v2h-2v2h-2v2H4V7h12z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/clone" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M5 3H3v2h2V3zm2 4h2v2H7V7zm4 0h2v2h-2V7zm2 12h-2v2h2v-2zm2 0h2v2h-2v-2zm6 0h-2v2h2v-2zM7 11h2v2H7v-2zm14 0h-2v2h2v-2zm-2 4h2v2h-2v-2zM7 19h2v2H7v-2zM19 7h2v2h-2V7zM7 3h2v2H7V3zm2 12H7v2h2v-2zM3 7h2v2H3V7zm14 0h-2v2h2V7zM3 11h2v2H3v-2zm2 4H3v2h2v-2zm6-12h2v2h-2V3zm6 0h-2v2h2V3z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/fill" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M21 3h-8v2h4v2h2v4h2V3zm-4 4h-2v2h-2v2h2V9h2V7zm-8 8h2v-2H9v2H7v2h2v-2zm-4-2v4h2v2H5h6v2H3v-8h2z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/home" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M14 2h-4v2H8v2H6v2H4v2H2v2h2v10h7v-6h2v6h7V12h2v-2h-2V8h-2V6h-2V4h-2V2zm0 2v2h2v2h2v2h2v2h-2v8h-3v-6H9v6H6v-8H4v-2h2V8h2V6h2V4h4z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/time" icon={<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24"> <path d="M20 0h2v2h2v2h-2v2h-2V4h-2V2h2V0ZM8 4h8v2h-2v2h-2V6H8V4ZM6 8V6h2v2H6Zm0 8H4V8h2v8Zm2 2H6v-2h2v2Zm8 0v2H8v-2h8Zm2-2v2h-2v-2h2Zm-2-4v-2h2V8h2v8h-2v-4h-2Zm-4 0h4v2h-4v-2Zm0 0V8h-2v4h2Zm-8 6H2v2H0v2h2v2h2v-2h2v-2H4v-2Z" /> </svg>} />
|
||||
<Button2 title="/gamerule" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M4 5h16v2H4V5zm0 12H2V7h2v10zm16 0v2H4v-2h16zm0 0h2V7h-2v10zm-2-8h-4v6h4V9z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/vanish" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M8 6h8v2H8V6zm-4 4V8h4v2H4zm-2 2v-2h2v2H2zm0 2v-2H0v2h2zm2 2H2v-2h2v2zm4 2H4v-2h4v2zm8 0v2H8v-2h8zm4-2v2h-4v-2h4zm2-2v2h-2v-2h2zm0-2h2v2h-2v-2zm-2-2h2v2h-2v-2zm0 0V8h-4v2h4zm-10 1h4v4h-4v-4z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/clear" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M16 2v4h6v2h-2v14H4V8H2V6h6V2h8zm-2 2h-4v2h4V4zm0 4H6v12h12V8h-4z" fill="currentColor" /> </svg>} />
|
||||
<Button2 title="/setspawnpoint" icon={<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M13 2v4h5v5h4v2h-4v5h-5v4h-2v-4H6v-5H2v-2h4V6h5V2h2zM8 8v8h8V8H8zm2 2h4v4h-4v-4z" fill="currentColor" /> </svg>} />
|
||||
</div>
|
||||
}
|
||||
const meta: Meta<any> = {
|
||||
component: Comp,
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<any>;
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
},
|
||||
}
|
||||
4
src/react/PixelartIcon.tsx
Normal file
4
src/react/PixelartIcon.tsx
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// names: https://pixelarticons.com/free/
|
||||
export default ({ iconName, width, styles = {}, className = undefined }) => {
|
||||
return <iconify-icon icon={`pixelarticons:${iconName}`} style={{ width, height: width, ...styles }} className={className} />
|
||||
}
|
||||
19
src/react/TouchAreas.stories.tsx
Normal file
19
src/react/TouchAreas.stories.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import TouchAreasControls from './TouchAreasControls'
|
||||
|
||||
const meta: Meta<typeof TouchAreasControls> = {
|
||||
component: TouchAreasControls,
|
||||
args: {
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof TouchAreasControls>;
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
touchActive: true,
|
||||
setupActive: true,
|
||||
},
|
||||
}
|
||||
102
src/react/TouchAreasControls.tsx
Normal file
102
src/react/TouchAreasControls.tsx
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import { CSSProperties, useEffect, useRef, useState } from 'react'
|
||||
import PixelartIcon from './PixelartIcon'
|
||||
|
||||
export type Button = 'action' | 'sneak' | 'break'
|
||||
|
||||
export default ({ touchActive, setupActive, buttonsPositions }) => {
|
||||
if (setupActive) touchActive = true
|
||||
|
||||
const [joystickPosition, setJoystickPosition] = useState(null as { x, y, pointerId } | null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!touchActive) return
|
||||
const controller = new AbortController()
|
||||
const { signal } = controller
|
||||
addEventListener('pointerdown', (e) => {
|
||||
if (e.pointerId === joystickPosition?.pointerId) {
|
||||
const x = e.clientX - joystickPosition.x
|
||||
const y = e.clientY - joystickPosition.y
|
||||
const supportsPressure = (e as any).pressure !== undefined && (e as any).pressure !== 0 && (e as any).pressure !== 0.5 && (e as any).pressure !== 1 && (e.pointerType === 'touch' || e.pointerType === 'pen')
|
||||
if ((e as any).pressure > 0.5) {
|
||||
}
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (e.clientX < window.innerWidth / 2) {
|
||||
setJoystickPosition({
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
pointerId: e.pointerId,
|
||||
})
|
||||
}
|
||||
}, {
|
||||
signal,
|
||||
})
|
||||
return () => {
|
||||
controller.abort()
|
||||
}
|
||||
}, [touchActive])
|
||||
|
||||
buttonsPositions = {
|
||||
// 0-100
|
||||
action: {
|
||||
x: 90,
|
||||
y: 70
|
||||
},
|
||||
sneak: {
|
||||
x: 90,
|
||||
y: 90
|
||||
},
|
||||
break: {
|
||||
x: 70,
|
||||
y: 70
|
||||
}
|
||||
}
|
||||
|
||||
const buttonStyles = (name: Button) => ({
|
||||
padding: 10,
|
||||
position: 'fixed',
|
||||
left: `${buttonsPositions[name].x}%`,
|
||||
top: `${buttonsPositions[name].y}%`,
|
||||
borderRadius: '50%',
|
||||
} satisfies CSSProperties)
|
||||
|
||||
return <div>
|
||||
<div
|
||||
className='movement_joystick_outer'
|
||||
style={{
|
||||
display: joystickPosition ? 'block' : 'none',
|
||||
borderRadius: '50%',
|
||||
width: 50,
|
||||
height: 50,
|
||||
border: '2px solid rgba(0, 0, 0, 0.5)',
|
||||
backgroundColor: 'rgba(255, 255, div, 0.5)',
|
||||
position: 'fixed',
|
||||
left: joystickPosition?.x,
|
||||
top: joystickPosition?.y,
|
||||
}}>
|
||||
<div
|
||||
className='movement_joystick_inner'
|
||||
style={{
|
||||
borderRadius: '50%',
|
||||
width: 20,
|
||||
height: 20,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
position: 'absolute',
|
||||
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={buttonStyles('action')}>
|
||||
<PixelartIcon width={10} iconName='circle' />
|
||||
</div>
|
||||
<div style={buttonStyles('sneak')}>
|
||||
<PixelartIcon width={10} iconName='arrow-down' />
|
||||
</div>
|
||||
<div style={buttonStyles('break')}>
|
||||
<PixelartIcon width={10} iconName='arrow-down' />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
12
src/react/TouchAreasControlsProvider.tsx
Normal file
12
src/react/TouchAreasControlsProvider.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { useSnapshot } from 'valtio'
|
||||
import { activeModalStack } from '../globalState'
|
||||
import TouchAreasControls from './TouchAreasControls'
|
||||
import { useIsModalActive, useUsingTouch } from './utils'
|
||||
|
||||
export default () => {
|
||||
const usingTouch = useUsingTouch()
|
||||
const hasModals = useSnapshot(activeModalStack).length !== 0
|
||||
const setupActive = useIsModalActive('touch-areas-setup')
|
||||
|
||||
return <TouchAreasControls touchActive={usingTouch && hasModals} setupActive={setupActive} />
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import TouchControls from './react/TouchControls'
|
|||
import widgets from './react/widgets'
|
||||
import { useIsWidgetActive } from './react/utils'
|
||||
import GlobalSearchInput from './GlobalSearchInput'
|
||||
import TouchAreasControlsProvider from './react/TouchAreasControlsProvider'
|
||||
|
||||
const Portal = ({ children, to }) => {
|
||||
return createPortal(children, to)
|
||||
|
|
@ -59,6 +60,7 @@ const InGameUi = () => {
|
|||
<DeathScreenProvider />
|
||||
<ChatProvider />
|
||||
<SoundMuffler />
|
||||
<TouchAreasControlsProvider />
|
||||
</Portal>
|
||||
<DisplayQr />
|
||||
<Portal to={document.body}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue