feat: add support for /ping command, fix chat fading!
This commit is contained in:
parent
3336680a0e
commit
0e4435ef91
6 changed files with 55 additions and 46 deletions
|
|
@ -189,23 +189,22 @@ input[type=text],
|
|||
background-color: rgba(0, 0, 0, 0.5);
|
||||
list-style: none;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.chat-message-fadeout {
|
||||
opacity: 1;
|
||||
transition: all 3s;
|
||||
}
|
||||
|
||||
.chat-message-fade {
|
||||
.chat-message-fading {
|
||||
opacity: 0;
|
||||
transition: opacity 3s ease-in-out;
|
||||
}
|
||||
|
||||
.chat-message-faded {
|
||||
transition: none !important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Ensure messages are always visible when chat is open */
|
||||
.chat.opened .chat-message {
|
||||
opacity: 1 !important;
|
||||
display: block !important;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,19 +11,37 @@ import { useScrollBehavior } from './hooks/useScrollBehavior'
|
|||
export type Message = {
|
||||
parts: MessageFormatPart[],
|
||||
id: number
|
||||
fading?: boolean
|
||||
faded?: boolean
|
||||
timestamp?: number
|
||||
}
|
||||
|
||||
const MessageLine = ({ message, currentPlayerName }: { message: Message, currentPlayerName?: string }) => {
|
||||
const MessageLine = ({ message, currentPlayerName, chatOpened }: { message: Message, currentPlayerName?: string, chatOpened?: boolean }) => {
|
||||
const [fadeState, setFadeState] = useState<'visible' | 'fading' | 'faded'>('visible')
|
||||
|
||||
useEffect(() => {
|
||||
// Start fading after 5 seconds
|
||||
const fadeTimeout = setTimeout(() => {
|
||||
setFadeState('fading')
|
||||
}, 5000)
|
||||
|
||||
// Remove after fade animation (3s) completes
|
||||
const removeTimeout = setTimeout(() => {
|
||||
setFadeState('faded')
|
||||
}, 8000)
|
||||
|
||||
// Cleanup timeouts if component unmounts
|
||||
return () => {
|
||||
clearTimeout(fadeTimeout)
|
||||
clearTimeout(removeTimeout)
|
||||
}
|
||||
}, []) // Empty deps array since we only want this to run once when message is added
|
||||
|
||||
const classes = {
|
||||
'chat-message-fadeout': message.fading,
|
||||
'chat-message-fade': message.fading,
|
||||
'chat-message-faded': message.faded,
|
||||
'chat-message': true
|
||||
'chat-message': true,
|
||||
'chat-message-fading': !chatOpened && fadeState === 'fading',
|
||||
'chat-message-faded': !chatOpened && fadeState === 'faded'
|
||||
}
|
||||
|
||||
return <li className={Object.entries(classes).filter(([, val]) => val).map(([name]) => name).join(' ')}>
|
||||
return <li className={Object.entries(classes).filter(([, val]) => val).map(([name]) => name).join(' ')} data-time={message.timestamp ? new Date(message.timestamp).toLocaleString('en-US', { hour12: false }) : undefined}>
|
||||
{message.parts.map((msg, i) => {
|
||||
// Check if this is a text part that might contain a mention
|
||||
if (msg.text && currentPlayerName) {
|
||||
|
|
@ -70,17 +88,6 @@ export const chatInputValueGlobal = proxy({
|
|||
value: ''
|
||||
})
|
||||
|
||||
export const fadeMessage = (message: Message, initialTimeout: boolean, requestUpdate: () => void) => {
|
||||
setTimeout(() => {
|
||||
message.fading = true
|
||||
requestUpdate()
|
||||
setTimeout(() => {
|
||||
message.faded = true
|
||||
requestUpdate()
|
||||
}, 3000)
|
||||
}, initialTimeout ? 5000 : 0)
|
||||
}
|
||||
|
||||
export default ({
|
||||
messages,
|
||||
opacity = 1,
|
||||
|
|
@ -372,7 +379,7 @@ export default ({
|
|||
</div>
|
||||
)}
|
||||
{messages.map((m) => (
|
||||
<MessageLine key={reactKeyForMessage(m)} message={m} currentPlayerName={playerNameValidated} />
|
||||
<MessageLine key={reactKeyForMessage(m)} message={m} currentPlayerName={playerNameValidated} chatOpened={opened} />
|
||||
))}
|
||||
</div> || undefined}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { getBuiltinCommandsList, tryHandleBuiltinCommand } from '../builtinComma
|
|||
import { gameAdditionalState, hideCurrentModal, miscUiState } from '../globalState'
|
||||
import { options } from '../optionsStorage'
|
||||
import { viewerVersionState } from '../viewerConnector'
|
||||
import Chat, { Message, fadeMessage } from './Chat'
|
||||
import Chat, { Message } from './Chat'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
import { hideNotification, notificationProxy, showNotification } from './NotificationProvider'
|
||||
import { getServerIndex, updateLoadedServerData } from './serversStorage'
|
||||
|
|
@ -16,6 +16,7 @@ export default () => {
|
|||
const [messages, setMessages] = useState([] as Message[])
|
||||
const isChatActive = useIsModalActive('chat')
|
||||
const lastMessageId = useRef(0)
|
||||
const lastPingTime = useRef(0)
|
||||
const usingTouch = useSnapshot(miscUiState).currentTouch
|
||||
const { chatSelect, messagesLimit, chatOpacity, chatOpacityOpened, chatVanillaRestrictions, debugChatScroll, chatPingExtension } = useSnapshot(options)
|
||||
const isUsingMicrosoftAuth = useMemo(() => !!lastConnectOptions.value?.authenticatedAccount, [])
|
||||
|
|
@ -29,18 +30,23 @@ export default () => {
|
|||
jsonMsg = jsonMsg['unsigned']
|
||||
}
|
||||
const parts = formatMessage(jsonMsg)
|
||||
const messageText = parts.map(part => part.text).join('')
|
||||
|
||||
// Handle ping response
|
||||
if (messageText === 'Pong!' && lastPingTime.current > 0) {
|
||||
const latency = Date.now() - lastPingTime.current
|
||||
parts.push({ text: ` Latency: ${latency}ms`, color: '#00ff00' })
|
||||
lastPingTime.current = 0
|
||||
}
|
||||
|
||||
setMessages(m => {
|
||||
lastMessageId.current++
|
||||
const newMessage: Message = {
|
||||
parts,
|
||||
id: lastMessageId.current,
|
||||
faded: false,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
fadeMessage(newMessage, true, () => {
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
setMessages(m => [...m])
|
||||
})
|
||||
|
||||
return [...m, newMessage].slice(-messagesLimit)
|
||||
})
|
||||
})
|
||||
|
|
@ -61,6 +67,11 @@ export default () => {
|
|||
return players.filter(name => (!value || name.toLowerCase().includes(value.toLowerCase())) && name !== bot.username).map(name => `@${name}`)
|
||||
}}
|
||||
sendMessage={async (message) => {
|
||||
// Record ping command time
|
||||
if (message === '/ping') {
|
||||
lastPingTime.current = Date.now()
|
||||
}
|
||||
|
||||
const builtinHandled = tryHandleBuiltinCommand(message)
|
||||
if (getServerIndex() !== undefined && (message.startsWith('/login') || message.startsWith('/register'))) {
|
||||
showNotification('Click here to save your password in browser for auto-login', undefined, false, undefined, () => {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ export default () => {
|
|||
fontFamily: 'minecraft, monospace',
|
||||
textAlign: 'center',
|
||||
zIndex: 1000,
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0
|
||||
}}>
|
||||
<div style={{
|
||||
fontSize: '16px',
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ const detectStorageConflicts = (): StorageConflict[] => {
|
|||
const localParsed = JSON.parse(localStorageValue)
|
||||
const cookieParsed = JSON.parse(cookieValue)
|
||||
|
||||
if (localParsed?.migrated) {
|
||||
if (localStorage.getItem(`${localStorageKey}:migrated`)) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -309,18 +309,7 @@ const markLocalStorageAsMigrated = (key: keyof StorageData) => {
|
|||
return
|
||||
}
|
||||
|
||||
const data = localStorage.getItem(localStorageKey)
|
||||
if (data) {
|
||||
try {
|
||||
const parsed = JSON.parse(data)
|
||||
localStorage.setItem(
|
||||
localStorageKey, JSON.stringify(typeof parsed === 'object' ? {
|
||||
...parsed, migrated: Date.now()
|
||||
} : { data: parsed, migrated: Date.now() })
|
||||
)
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
localStorage.setItem(`${localStorageKey}:migrated`, 'true')
|
||||
}
|
||||
|
||||
const saveKey = (key: keyof StorageData) => {
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ const App = () => {
|
|||
<div />
|
||||
</RobustPortal>
|
||||
<EnterFullscreenButton />
|
||||
<StorageConflictModal />
|
||||
<InGameUi />
|
||||
<RobustPortal to={document.querySelector('#ui-root')}>
|
||||
<AllWidgets />
|
||||
|
|
@ -248,7 +249,6 @@ const App = () => {
|
|||
|
||||
<SelectOption />
|
||||
<CreditsAboutModal />
|
||||
<StorageConflictModal />
|
||||
<NoModalFoundProvider />
|
||||
</RobustPortal>
|
||||
<RobustPortal to={document.body}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue