feat: new reworked notifications
feat: support for world icons in singleplayer menu
This commit is contained in:
parent
14460a62b2
commit
d7bdf3633d
17 changed files with 214 additions and 140 deletions
|
|
@ -49,8 +49,9 @@
|
|||
"esbuild": "^0.19.3",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"express": "^4.18.2",
|
||||
"flying-squid": "npm:@zardoy/flying-squid@^0.0.12",
|
||||
"flying-squid": "npm:@zardoy/flying-squid@^0.0.13",
|
||||
"fs-extra": "^11.1.1",
|
||||
"google-drive-browserfs": "github:zardoy/browserfs#google-drive",
|
||||
"iconify-icon": "^1.0.8",
|
||||
"jszip": "^3.10.1",
|
||||
"lit": "^2.8.0",
|
||||
|
|
@ -61,6 +62,7 @@
|
|||
"node-gzip": "^1.1.2",
|
||||
"peerjs": "^1.5.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
|
||||
"qrcode.react": "^3.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
@ -74,8 +76,7 @@
|
|||
"title-case": "3.x",
|
||||
"ua-parser-js": "^1.0.37",
|
||||
"valtio": "^1.11.1",
|
||||
"workbox-build": "^7.0.0",
|
||||
"google-drive-browserfs": "github:zardoy/browserfs#google-drive"
|
||||
"workbox-build": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-essentials": "^7.4.6",
|
||||
|
|
|
|||
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
|
|
@ -85,8 +85,8 @@ importers:
|
|||
specifier: ^4.18.2
|
||||
version: 4.18.2
|
||||
flying-squid:
|
||||
specifier: npm:@zardoy/flying-squid@^0.0.12
|
||||
version: /@zardoy/flying-squid@0.0.12
|
||||
specifier: npm:@zardoy/flying-squid@^0.0.13
|
||||
version: /@zardoy/flying-squid@0.0.13
|
||||
fs-extra:
|
||||
specifier: ^11.1.1
|
||||
version: 11.1.1
|
||||
|
|
@ -123,6 +123,9 @@ importers:
|
|||
pretty-bytes:
|
||||
specifier: ^6.1.1
|
||||
version: 6.1.1
|
||||
prismarine-provider-anvil:
|
||||
specifier: github:zardoy/prismarine-provider-anvil#everything
|
||||
version: github.com/zardoy/prismarine-provider-anvil/0ddcd9d48574113308e1fbebef60816aced0846f(minecraft-data@3.62.0)
|
||||
qrcode.react:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0(react@18.2.0)
|
||||
|
|
@ -5655,8 +5658,8 @@ packages:
|
|||
tslib: 1.14.1
|
||||
dev: true
|
||||
|
||||
/@zardoy/flying-squid@0.0.12:
|
||||
resolution: {integrity: sha512-wFvdROB9iEucdYamBLXhKKGiUdprjxJsSo0Mk4UKMUoau9G3oly1tVfkuVZc9mQm0NSLOx8oSPA3uCNUT9lAgw==}
|
||||
/@zardoy/flying-squid@0.0.13:
|
||||
resolution: {integrity: sha512-K4vjMx+pi+Xbmm/m6xb17hml8w+0Bk89SiqGuDiB5zaRcTup7K5iqmZ2STQRrTZkjxSNh+eX26R67x2l0XHBIg==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
|
|
@ -5671,7 +5674,7 @@ packages:
|
|||
minecraft-data: 3.62.0
|
||||
minecraft-protocol: github.com/zardoy/minecraft-protocol/2c14a686bfe7cbd9a5c87b629b402295ee86219f
|
||||
mkdirp: 2.1.6
|
||||
moment: 2.29.4
|
||||
moment: 2.30.1
|
||||
needle: 2.9.1
|
||||
node-gzip: 1.1.2
|
||||
node-rsa: 1.1.1
|
||||
|
|
@ -11149,8 +11152,8 @@ packages:
|
|||
dependencies:
|
||||
nearley: 2.20.1
|
||||
|
||||
/moment@2.29.4:
|
||||
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
|
||||
/moment@2.30.1:
|
||||
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
|
||||
dev: false
|
||||
|
||||
/moo@0.5.2:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import * as nbt from 'prismarine-nbt'
|
|||
import RegionFile from 'prismarine-provider-anvil/src/region'
|
||||
import { versions } from 'minecraft-data'
|
||||
import { openWorldDirectory, openWorldZip } from './browserfs'
|
||||
import { isGameActive, showNotification } from './globalState'
|
||||
import { isGameActive } from './globalState'
|
||||
import { showNotification } from './react/NotificationProvider'
|
||||
|
||||
const parseNbt = promisify(nbt.parse)
|
||||
const simplifyNbt = nbt.simplify
|
||||
|
|
@ -100,9 +101,7 @@ async function handleDroppedFile (file: File) {
|
|||
alert('Couldn\'t parse nbt, ensure you are opening .dat or file (or .zip/folder with a world)')
|
||||
throw err
|
||||
})
|
||||
showNotification({
|
||||
message: `${file.name} data available in browser console`,
|
||||
})
|
||||
showNotification(`${file.name} data available in browser console`)
|
||||
console.log('raw', parsed)
|
||||
console.log('simplified', nbt.simplify(parsed))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,17 +158,4 @@ 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 = (newNotification: Partial<typeof notification>) => {
|
||||
Object.assign(notification, { show: true, ...newNotification }, initialNotification)
|
||||
}
|
||||
|
||||
// todo restore auto-save on interval for player data! (or implement it in flying squid since there is already auto-save for world)
|
||||
|
|
|
|||
11
src/index.ts
11
src/index.ts
|
|
@ -60,7 +60,6 @@ import {
|
|||
insertActiveModalStack,
|
||||
isGameActive,
|
||||
miscUiState,
|
||||
notification
|
||||
} from './globalState'
|
||||
|
||||
|
||||
|
|
@ -95,6 +94,8 @@ import { downloadSoundsIfNeeded } from './soundSystem'
|
|||
import { ua } from './react/utils'
|
||||
import { handleMovementStickDelta, joystickPointer } from './react/TouchAreasControls'
|
||||
import { possiblyHandleStateVariable } from './googledrive'
|
||||
import flyingSquidEvents from './flyingSquidEvents'
|
||||
import { hideNotification, notificationProxy } from './react/NotificationProvider'
|
||||
|
||||
window.debug = debug
|
||||
window.THREE = THREE
|
||||
|
|
@ -366,6 +367,7 @@ async function connect (connectOptions: {
|
|||
})
|
||||
}
|
||||
}
|
||||
let lastPacket = undefined as string | undefined
|
||||
const onPossibleErrorDisconnect = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
||||
if (lastPacket && bot?._client && bot._client.state !== 'play') {
|
||||
|
|
@ -373,6 +375,7 @@ async function connect (connectOptions: {
|
|||
}
|
||||
}
|
||||
const handleError = (err) => {
|
||||
console.error(err)
|
||||
errorAbortController.abort()
|
||||
if (isCypress()) throw err
|
||||
if (miscUiState.gameLoaded) return
|
||||
|
|
@ -471,6 +474,7 @@ async function connect (connectOptions: {
|
|||
setLoadingScreenStatus(newStatus, false, false, true)
|
||||
})
|
||||
})
|
||||
flyingSquidEvents()
|
||||
}
|
||||
|
||||
let initialLoadingText: string
|
||||
|
|
@ -570,7 +574,6 @@ async function connect (connectOptions: {
|
|||
destroyAll()
|
||||
})
|
||||
|
||||
let lastPacket = undefined as string | undefined
|
||||
const packetBeforePlay = (_, __, ___, fullBuffer) => {
|
||||
lastPacket = fullBuffer.toString()
|
||||
}
|
||||
|
|
@ -676,7 +679,9 @@ async function connect (connectOptions: {
|
|||
}
|
||||
|
||||
function changeCallback () {
|
||||
notification.show = false
|
||||
if (notificationProxy.id === 'pointerlockchange') {
|
||||
hideNotification()
|
||||
}
|
||||
if (renderer.xr.isPresenting) return // todo
|
||||
if (!pointerLock.hasPointerLock && activeModalStack.length === 0) {
|
||||
showModal(pauseMenu)
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ export const loadSave = async (root = '/world') => {
|
|||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete forceCachedDataPaths[key]
|
||||
}
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const key in forceRedirectPaths) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete forceRedirectPaths[key]
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
//@ts-check
|
||||
|
||||
// create lit element
|
||||
const { LitElement, html, css } = require('lit')
|
||||
const { subscribe } = require('valtio')
|
||||
const { notification } = require('../globalState')
|
||||
|
||||
class Notification extends LitElement {
|
||||
static get properties () {
|
||||
return {
|
||||
renderHtml: { type: Boolean },
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.renderHtml = false
|
||||
let timeout
|
||||
subscribe(notification, () => {
|
||||
if (timeout) clearTimeout(timeout)
|
||||
this.requestUpdate()
|
||||
if (!notification.show) return
|
||||
this.renderHtml = true
|
||||
if (!notification.autoHide) return
|
||||
timeout = setTimeout(() => {
|
||||
notification.show = false
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
if (!this.renderHtml) return
|
||||
const show = notification.show && notification.message
|
||||
return html`
|
||||
<div @transitionend=${this.ontransitionend} class="notification notification-${notification.type} ${show ? 'notification-show' : ''}">
|
||||
${notification.message}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
ontransitionend = (event) => {
|
||||
if (event.propertyName !== 'opacity') return
|
||||
|
||||
if (!notification.show) {
|
||||
this.renderHtml = false
|
||||
}
|
||||
}
|
||||
|
||||
static get styles () {
|
||||
return css`
|
||||
.notification {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
min-width: 200px;
|
||||
padding: 10px;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
background: #000;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.notification-info {
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.notification-error {
|
||||
background: #d00;
|
||||
}
|
||||
|
||||
.notification-show {
|
||||
opacity: 1;
|
||||
}
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('pmui-notification', Notification)
|
||||
|
|
@ -4,7 +4,7 @@ const { LitElement, html, css } = require('lit')
|
|||
const { subscribe } = require('valtio')
|
||||
const { subscribeKey } = require('valtio/utils')
|
||||
const { usedServerPathsV1 } = require('flying-squid/dist/lib/modules/world')
|
||||
const { hideCurrentModal, showModal, miscUiState, notification, openOptionsMenu } = require('../globalState')
|
||||
const { hideCurrentModal, showModal, miscUiState, openOptionsMenu } = require('../globalState')
|
||||
const { fsState } = require('../loadSave')
|
||||
const { openGithub, setLoadingScreenStatus } = require('../utils')
|
||||
const { disconnect } = require('../flyingSquidUtils')
|
||||
|
|
@ -143,8 +143,6 @@ class PauseScreen extends LitElement {
|
|||
|
||||
show () {
|
||||
this.focus()
|
||||
// todo?
|
||||
notification.show = false
|
||||
}
|
||||
|
||||
onReturnPress () {
|
||||
|
|
|
|||
71
src/react/Notification.tsx
Normal file
71
src/react/Notification.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { Transition } from 'react-transition-group'
|
||||
import PixelartIcon from './PixelartIcon'
|
||||
|
||||
// slide up
|
||||
const startStyle = { opacity: 0, transform: 'translateY(100%)' }
|
||||
const endExitStyle = { opacity: 0, transform: 'translateY(-100%)' }
|
||||
const endStyle = { opacity: 1, transform: 'translateY(0)' }
|
||||
|
||||
const stateStyles = {
|
||||
entering: startStyle,
|
||||
entered: endStyle,
|
||||
exiting: endExitStyle,
|
||||
exited: endExitStyle,
|
||||
}
|
||||
const duration = 200
|
||||
const basicStyle = {
|
||||
transition: `${duration}ms ease-in-out all`,
|
||||
}
|
||||
|
||||
// save pass: login
|
||||
|
||||
export default ({ type = 'message', message, subMessage = '', open, icon = '', action = undefined as (() => void) | undefined }) => {
|
||||
const isError = type === 'error'
|
||||
icon ||= isError ? 'alert' : 'message'
|
||||
|
||||
return <Transition
|
||||
in={open}
|
||||
timeout={duration}
|
||||
mountOnEnter
|
||||
unmountOnExit
|
||||
>
|
||||
{state => {
|
||||
const addStyles = { ...basicStyle, ...stateStyles[state] }
|
||||
|
||||
return <div className={`app-notification ${isError ? 'error-notification' : ''}`} onClick={action} style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
right: 0,
|
||||
width: '160px',
|
||||
whiteSpace: 'nowrap',
|
||||
fontSize: '9px',
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
alignItems: 'center',
|
||||
padding: '3px 8px',
|
||||
background: isError ? 'rgba(255, 0, 0, 0.7)' : 'rgba(0, 0, 0, 0.7)',
|
||||
borderRadius: '0 0 0 5px',
|
||||
pointerEvents: action ? 'auto' : 'none',
|
||||
zIndex: 1200, // even above stats
|
||||
...addStyles
|
||||
}}>
|
||||
<PixelartIcon iconName={icon} styles={{ fontSize: 10 }} />
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
}}>
|
||||
<div>
|
||||
{message}
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: '7px',
|
||||
whiteSpace: 'nowrap',
|
||||
color: 'lightgray',
|
||||
}}>{subMessage}</div>
|
||||
</div>
|
||||
</div>
|
||||
}}
|
||||
</Transition>
|
||||
|
||||
}
|
||||
67
src/react/NotificationProvider.tsx
Normal file
67
src/react/NotificationProvider.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import { proxy, useSnapshot } from 'valtio'
|
||||
import Notification from './Notification'
|
||||
|
||||
type NotificationType = React.ComponentProps<typeof Notification> & {
|
||||
autoHide: boolean
|
||||
id: string
|
||||
}
|
||||
|
||||
// todo stacking
|
||||
export const notificationProxy = proxy({
|
||||
message: '',
|
||||
open: false,
|
||||
type: 'message',
|
||||
subMessage: '',
|
||||
icon: '',
|
||||
autoHide: true,
|
||||
id: '',
|
||||
} satisfies NotificationType as NotificationType)
|
||||
|
||||
export const showNotification = (
|
||||
message: string,
|
||||
subMessage = '',
|
||||
isError = false,
|
||||
icon = '',
|
||||
action = undefined as (() => void) | undefined,
|
||||
autoHide = true
|
||||
) => {
|
||||
notificationProxy.message = message
|
||||
notificationProxy.subMessage = subMessage
|
||||
notificationProxy.type = isError ? 'error' : 'message'
|
||||
notificationProxy.icon = icon
|
||||
notificationProxy.open = true
|
||||
notificationProxy.autoHide = autoHide
|
||||
notificationProxy.action = action
|
||||
}
|
||||
export const hideNotification = () => {
|
||||
// openNotification('') // reset
|
||||
notificationProxy.open = false
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const { autoHide, message, open, icon, type, subMessage } = useSnapshot(notificationProxy)
|
||||
|
||||
useEffect(() => {
|
||||
if (autoHide && open) {
|
||||
setTimeout(() => {
|
||||
hideNotification()
|
||||
}, 7000)
|
||||
}
|
||||
}, [autoHide, open])
|
||||
|
||||
// test
|
||||
// useEffect(() => {
|
||||
// setTimeout(() => {
|
||||
// openNotification('test', 'test', false, 'message')
|
||||
// }, 1000)
|
||||
// }, [])
|
||||
|
||||
return <Notification
|
||||
type={type}
|
||||
message={message}
|
||||
subMessage={subMessage}
|
||||
open={open}
|
||||
icon={icon}
|
||||
/>
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ import Tabs from './Tabs'
|
|||
export interface WorldProps {
|
||||
name: string
|
||||
title: string
|
||||
iconBase64?: string
|
||||
size?: number
|
||||
lastPlayed?: number
|
||||
isFocused?: boolean
|
||||
|
|
@ -22,7 +23,7 @@ export interface WorldProps {
|
|||
onInteraction?(interaction: 'enter' | 'space')
|
||||
}
|
||||
|
||||
const World = ({ name, isFocused, title, lastPlayed, size, detail = '', onFocus, onInteraction }: WorldProps) => {
|
||||
const World = ({ name, isFocused, title, lastPlayed, size, detail = '', onFocus, onInteraction, iconBase64 }: WorldProps) => {
|
||||
const timeRelativeFormatted = useMemo(() => {
|
||||
if (!lastPlayed) return
|
||||
const formatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
|
||||
|
|
@ -47,7 +48,7 @@ const World = ({ name, isFocused, title, lastPlayed, size, detail = '', onFocus,
|
|||
onInteraction?.(e.code === 'Enter' ? 'enter' : 'space')
|
||||
}
|
||||
}} onDoubleClick={() => onInteraction?.('enter')}>
|
||||
<img className={styles.world_image} src={missingWorldPreview} />
|
||||
<img className={`${styles.world_image} ${iconBase64 ? '' : styles.image_missing}`} src={iconBase64 ? `data:image/png;base64,${iconBase64}` : missingWorldPreview} alt='world preview' />
|
||||
<div className={styles.world_info}>
|
||||
<div className={styles.world_title} title='level.dat world name'>{title}</div>
|
||||
<div className='muted'>{timeRelativeFormatted} {detail.slice(-30)}</div>
|
||||
|
|
@ -123,8 +124,8 @@ export default ({ worldData, onGeneralAction, onWorldAction, activeProvider, set
|
|||
}
|
||||
{
|
||||
worldData
|
||||
? worldData.filter(data => data.title.toLowerCase().includes(search.toLowerCase())).map(({ name, title, size, lastPlayed, detail }) => (
|
||||
<World title={title} lastPlayed={lastPlayed} size={size} name={name} onFocus={setFocusedWorld} isFocused={focusedWorld === name} key={name} onInteraction={(interaction) => {
|
||||
? worldData.filter(data => data.title.toLowerCase().includes(search.toLowerCase())).map(({ name, size, detail, ...rest }) => (
|
||||
<World {...rest} size={size} name={name} onFocus={setFocusedWorld} isFocused={focusedWorld === name} key={name} onInteraction={(interaction) => {
|
||||
if (interaction === 'enter') onWorldAction('load', name)
|
||||
else if (interaction === 'space') firstButton.current?.focus()
|
||||
}} detail={detail} />
|
||||
|
|
|
|||
|
|
@ -30,12 +30,14 @@ const providersEnableFeatures = {
|
|||
calculateSize: true,
|
||||
delete: true,
|
||||
export: true,
|
||||
icon: true
|
||||
},
|
||||
google: {
|
||||
calculateSize: false,
|
||||
// TODO
|
||||
delete: false,
|
||||
export: false,
|
||||
icon: true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,12 +69,22 @@ export const readWorlds = (abortController: AbortController) => {
|
|||
size += stat.size
|
||||
}
|
||||
}
|
||||
let iconBase64 = ''
|
||||
if (providersEnableFeatures[provider].icon) {
|
||||
const iconPath = `${worldsPath}/${folder}/icon.png`
|
||||
try {
|
||||
iconBase64 = await fs.promises.readFile(iconPath, 'base64')
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
const levelName = levelDat.LevelName as string | undefined
|
||||
return {
|
||||
name: folder,
|
||||
title: levelName ?? folder,
|
||||
lastPlayed: levelDat.LastPlayed && longArrayToNumber(levelDat.LastPlayed),
|
||||
detail: `${levelDat.Version?.Name ?? 'unknown version'}, ${folder}`,
|
||||
iconBase64,
|
||||
size,
|
||||
} satisfies WorldProps
|
||||
}))).filter((x, i) => {
|
||||
|
|
|
|||
|
|
@ -36,9 +36,11 @@
|
|||
}
|
||||
.world_image {
|
||||
height: 100%;
|
||||
filter: grayscale(1);
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
.world_image.image_missing {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
.world_root.world_focused {
|
||||
border-color: white;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
//@ts-check
|
||||
import { renderToDom } from '@zardoy/react-util'
|
||||
|
||||
import { renderToDom, ErrorBoundary } from '@zardoy/react-util'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { QRCodeSVG } from 'qrcode.react'
|
||||
import { createPortal } from 'react-dom'
|
||||
|
|
@ -22,9 +21,10 @@ import widgets from './react/widgets'
|
|||
import { useIsWidgetActive } from './react/utils'
|
||||
import GlobalSearchInput from './GlobalSearchInput'
|
||||
import TouchAreasControlsProvider from './react/TouchAreasControlsProvider'
|
||||
import NotificationProvider from './react/NotificationProvider'
|
||||
|
||||
const Portal = ({ children, to }) => {
|
||||
return createPortal(children, to)
|
||||
const RobustPortal = ({ children, to }) => {
|
||||
return createPortal(<PerComponentErrorBoundary>{children}</PerComponentErrorBoundary>, to)
|
||||
}
|
||||
|
||||
const DisplayQr = () => {
|
||||
|
|
@ -57,7 +57,7 @@ const InGameUi = () => {
|
|||
if (!gameLoaded) return
|
||||
|
||||
return <>
|
||||
<Portal to={document.querySelector('#ui-root')}>
|
||||
<RobustPortal to={document.querySelector('#ui-root')}>
|
||||
{/* apply scaling */}
|
||||
<DeathScreenProvider />
|
||||
<ChatProvider />
|
||||
|
|
@ -65,13 +65,13 @@ const InGameUi = () => {
|
|||
<TitleProvider />
|
||||
<ScoreboardProvider />
|
||||
<TouchAreasControlsProvider />
|
||||
</Portal>
|
||||
</RobustPortal>
|
||||
<DisplayQr />
|
||||
<Portal to={document.body}>
|
||||
<RobustPortal to={document.body}>
|
||||
{/* becaues of z-index */}
|
||||
<TouchControls />
|
||||
<GlobalSearchInput />
|
||||
</Portal>
|
||||
</RobustPortal>
|
||||
</>
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ const App = () => {
|
|||
return <div>
|
||||
<EnterFullscreenButton />
|
||||
<InGameUi />
|
||||
<Portal to={document.querySelector('#ui-root')}>
|
||||
<RobustPortal to={document.querySelector('#ui-root')}>
|
||||
<AllWidgets />
|
||||
<SingleplayerProvider />
|
||||
<CreateWorldProvider />
|
||||
|
|
@ -98,10 +98,20 @@ const App = () => {
|
|||
<SelectOption />
|
||||
<OptionsRenderApp />
|
||||
<MainMenuRenderApp />
|
||||
</Portal>
|
||||
<NotificationProvider />
|
||||
</RobustPortal>
|
||||
</div>
|
||||
}
|
||||
|
||||
const PerComponentErrorBoundary = ({ children }) => {
|
||||
return children.map((child, i) => <ErrorBoundary key={i} renderError={(error) => {
|
||||
// notfic
|
||||
const componentNameClean = (child.type.name || child.type.displayName || 'Unknown').replaceAll(/__|_COMPONENT/g, '')
|
||||
console.error(`UI component ${componentNameClean} crashed!`, componentNameClean, error.message)
|
||||
return null
|
||||
}}>{child}</ErrorBoundary>)
|
||||
}
|
||||
|
||||
renderToDom(<App />, {
|
||||
strictMode: false,
|
||||
selector: '#react-root',
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ import { Vec3 } from 'vec3'
|
|||
import { versionToNumber } from 'prismarine-viewer/viewer/prepare/utils'
|
||||
import { loadScript } from 'prismarine-viewer/viewer/lib/utils'
|
||||
import type { Block } from 'prismarine-block'
|
||||
import { miscUiState, showNotification } from './globalState'
|
||||
import { miscUiState } from './globalState'
|
||||
import { options } from './optionsStorage'
|
||||
import { loadOrPlaySound } from './basicSounds'
|
||||
import { showNotification } from './react/NotificationProvider'
|
||||
|
||||
subscribeKey(miscUiState, 'gameLoaded', async () => {
|
||||
if (!miscUiState.gameLoaded) return
|
||||
|
|
@ -21,9 +22,7 @@ subscribeKey(miscUiState, 'gameLoaded', async () => {
|
|||
|
||||
if (!soundsMap) {
|
||||
console.warn('No sounds map for version', bot.version, 'supported versions are', Object.keys(allSoundsMap).join(', '))
|
||||
showNotification({
|
||||
message: 'No sounds map for version ' + bot.version,
|
||||
})
|
||||
showNotification('Warning', 'No sounds map for version ' + bot.version)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import blocksFileNames from '../generated/blocks.json'
|
|||
import type { BlockStates } from './playerWindows'
|
||||
import { copyFilesAsync, copyFilesAsyncWithProgress, mkdirRecursive, removeFileRecursiveAsync } from './browserfs'
|
||||
import { setLoadingScreenStatus } from './utils'
|
||||
import { showNotification } from './globalState'
|
||||
import { showNotification } from './react/NotificationProvider'
|
||||
|
||||
export const resourcePackState = proxy({
|
||||
resourcePackInstalled: false,
|
||||
|
|
@ -96,9 +96,7 @@ export const completeTexturePackInstall = async (name?: string) => {
|
|||
await genTexturePackTextures(viewer.version)
|
||||
}
|
||||
setLoadingScreenStatus(undefined)
|
||||
showNotification({
|
||||
message: 'Texturepack installed!',
|
||||
})
|
||||
showNotification('Texturepack installed')
|
||||
await updateTexturePackInstalledState()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { hideModal, isGameActive, miscUiState, notification, showModal } from './globalState'
|
||||
import { hideModal, isGameActive, miscUiState, showModal } from './globalState'
|
||||
import { options } from './optionsStorage'
|
||||
import { appStatusState } from './react/AppStatusProvider'
|
||||
import { notificationProxy, showNotification } from './react/NotificationProvider'
|
||||
|
||||
export const goFullscreen = async (doToggle = false) => {
|
||||
if (!document.fullscreenElement) {
|
||||
|
|
@ -32,8 +33,8 @@ export const pointerLock = {
|
|||
void goFullscreen()
|
||||
}
|
||||
const displayBrowserProblem = () => {
|
||||
notification.show = true
|
||||
notification.message = navigator['keyboard'] ? 'Browser Limitation: Click on screen, enable Auto Fullscreen or F11' : 'Browser Limitation: Click on screen or use fullscreen in Chrome'
|
||||
showNotification('Browser Delay Limitation', navigator['keyboard'] ? 'Click on screen, enable Auto Fullscreen or F11' : 'Click on screen or use fullscreen in Chrome')
|
||||
notificationProxy.id = 'pointerlockchange'
|
||||
}
|
||||
if (!(document.fullscreenElement && navigator['keyboard']) && this.justHitEscape) {
|
||||
displayBrowserProblem()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue