diff --git a/README.md b/README.md index 71ec0e2da..deaf00cd3 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ - Monitoring: - Auto Restart FXServer on crash or hang - Server’s CPU/RAM consumption - - Live Console (with log file and command history) + - Live Console (with log file, command history and search) - Server tick time performance chart with player count ([example](https://i.imgur.com/VG8hpzr.gif)) - Server Activity Log (connections/disconnections, kills, chat, explosions and [custom commands](docs/custom_serverlog.md)) - Player Manager: @@ -63,7 +63,7 @@ - Scheduled restarts with warning announcements and custom events ([more info](docs/events.md)) - Translation Support ([more info](docs/translation.md)) - FiveM's Server CFG editor & validator -- Responsive(ish) web interface with Dark Mode 😎 +- Responsive web interface with Dark Mode 😎 Also, check our [Feature Graveyard](docs/feature_graveyard.md) for the features that are no longer among us (RIP). diff --git a/docs/dev_notes.md b/docs/dev_notes.md index 111624153..b76778aa8 100644 --- a/docs/dev_notes.md +++ b/docs/dev_notes.md @@ -23,8 +23,8 @@ - [x] fix z-order, cant click postpone/support on the warning bar - [x] Update packages for all workspaces + root - [x] FIXME: deployer "show db advanced options" has broken css and causes tx inception -- [ ] Check layout on 4k and ultrawide screens -- [ ] global hotkey to go to the player filter +- [x] Check layout on 4k and ultrawide screens +- [x] global hotkey to go to the player filter - careful to also handle child iframe and terminal canvas - add util function that gets a KeyboardEvent and returns the name of the hotkey pressed, then apply it to child iframe, global keydown handler, and xterm - [ ] FIXME: check if we need or can do something to prevent NUI CSRF @@ -52,6 +52,7 @@ - [ ] fix(nui/PlayerModel): require OneSync for bring and goto (PR #851) - [ ] fix issue where the forced password change on save reloads the page instead of moving to the identifiers tab - [ ] Remove old live console menu links +- [ ] Add clear copyright/license notice at the bottom of the server sidebar? - [ ] onesync should be legacy by default - [ ] check all discord invites (use utm params maybe?) diff --git a/panel/src/global.d.ts b/panel/src/global.d.ts index 696b687ff..dcbb9d658 100644 --- a/panel/src/global.d.ts +++ b/panel/src/global.d.ts @@ -1,10 +1,15 @@ -import { InjectedTxConsts } from '@shared/otherTypes'; +import type { InjectedTxConsts } from '@shared/otherTypes'; +import type { GlobalHotkeyAction } from './lib/hotkeyEventListener'; type LogoutNoticeMessage = { type: 'logoutNotice' }; type OpenAccountModalMessage = { type: 'openAccountModal' }; type OpenPlayerModalMessage = { type: 'openPlayerModal', ref: PlayerModalRefType }; type navigateToPageMessage = { type: 'navigateToPage', href: string }; type liveConsoleSearchHotkeyMessage = { type: 'liveConsoleSearchHotkey', action: string }; +type globalHotkeyMessage = { + type: 'globalHotkey'; + action: GlobalHotkeyAction; +}; export declare global { interface Window { @@ -16,5 +21,6 @@ export declare global { | MessageEvent | MessageEvent | MessageEvent - | MessageEvent; + | MessageEvent + | MessageEvent; } diff --git a/panel/src/hooks/useTheme.ts b/panel/src/hooks/useTheme.ts index 0663bdb87..7a4c23bc8 100644 --- a/panel/src/hooks/useTheme.ts +++ b/panel/src/hooks/useTheme.ts @@ -49,7 +49,7 @@ const applyNewTheme = (oldTheme: string, newTheme: string) => { } //Changing iframe theme - const iframeBody = (document.getElementById('legacyPageiframe') as HTMLObjectElement)?.contentDocument?.body; + const iframeBody = (document.getElementById('legacyPageIframe') as HTMLObjectElement)?.contentDocument?.body; if (iframeBody) { if (iframeTheme === 'dark') { iframeBody.classList.add('theme--dark'); diff --git a/panel/src/layout/MainShell.tsx b/panel/src/layout/MainShell.tsx index d5dbda447..556fa8ef2 100644 --- a/panel/src/layout/MainShell.tsx +++ b/panel/src/layout/MainShell.tsx @@ -17,6 +17,7 @@ import { navigate as setLocation } from 'wouter/use-location'; import MainSocket from './MainSocket'; import { TooltipProvider } from '@/components/ui/tooltip'; import { useToggleTheme } from '@/hooks/useTheme'; +import { hotkeyEventListener } from '@/lib/hotkeyEventListener'; import BreakpointDebugger from '@/components/BreakpointDebugger'; @@ -26,7 +27,7 @@ export default function MainShell() { const openPlayerModal = useOpenPlayerModal(); const toggleTheme = useToggleTheme(); - //Listener for messages from child iframes (legacy routes) + //Listener for messages from child iframes (legacy routes) or other sources useEventListener('message', (e: TxMessageEvent) => { if (e.data.type === 'logoutNotice') { expireSession('child iframe', 'got logoutNotice'); @@ -36,24 +37,14 @@ export default function MainShell() { openPlayerModal(e.data.ref); } else if (e.data.type === 'navigateToPage') { setLocation(e.data.href); + } else if (e.data.type === 'globalHotkey' && e.data.action === 'toggleLightMode') { + toggleTheme(); } }); - //Listens to hotkeys - DEBUG only for now + //Listens to hotkeys //NOTE: WILL NOT WORK IF THE FOCUS IS ON THE IFRAME - useEventListener('keydown', (e: KeyboardEvent) => { - if (!window.txConsts.showAdvanced) return; - if (e.ctrlKey && e.key === 'k') { - const el = document.getElementById('playerlistFilter'); - if (el) { - el.focus(); - e.preventDefault(); - } - } else if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'l') { - toggleTheme(); - e.preventDefault(); - } - }); + useEventListener('keydown', hotkeyEventListener); return <> diff --git a/panel/src/layout/playerlistSidebar/Playerlist.tsx b/panel/src/layout/playerlistSidebar/Playerlist.tsx index 0dc659b10..885dd8765 100644 --- a/panel/src/layout/playerlistSidebar/Playerlist.tsx +++ b/panel/src/layout/playerlistSidebar/Playerlist.tsx @@ -19,6 +19,8 @@ import { DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { useOpenPlayerModal } from "@/hooks/playerModal"; +import InlineCode from "@/components/InlineCode"; +import { useEventListener } from "usehooks-ts"; //NOTE: Move the styles (except color) to global.css since this component is rendered often @@ -40,11 +42,18 @@ type PlayerlistFilterProps = { setFilterString: (s: string) => void; }; function PlayerlistFilter({ filterString, setFilterString }: PlayerlistFilterProps) { + const inputRef = useRef(null); + useEventListener('message', (e: TxMessageEvent) => { + if (e.data.type === 'globalHotkey' && e.data.action === 'focusPlayerlistFilter') { + inputRef.current?.focus(); + } + }); + return (
- {filterString && } + {filterString ? ( + + ) : ( +
+ ctrl+k +
+ )}
{/* @@ -133,8 +148,8 @@ function PlayerlistFilter({ filterString, setFilterString }: PlayerlistFilterPro const PlayerlistFilterMemo = memo(PlayerlistFilter); -type PlayerlistPlayerProps = { - virtualItem: VirtualItem, +type PlayerlistPlayerProps = { + virtualItem: VirtualItem, player: PlayerlistPlayerType, modalOpener: (netid: number) => void, } @@ -196,8 +211,8 @@ export default function Playerlist() { const virtualItems = rowVirtualizer.getVirtualItems(); const modalOpener = (netid: number) => { - if(!serverMutex) return; - openPlayerModal({mutex: serverMutex, netid}); + if (!serverMutex) return; + openPlayerModal({ mutex: serverMutex, netid }); } return ( diff --git a/panel/src/lib/hotkeyEventListener.ts b/panel/src/lib/hotkeyEventListener.ts new file mode 100644 index 000000000..e7e2b5e37 --- /dev/null +++ b/panel/src/lib/hotkeyEventListener.ts @@ -0,0 +1,37 @@ +import { throttle } from "throttle-debounce"; + +export type GlobalHotkeyAction = 'focusPlayerlistFilter' | 'toggleLightMode'; + +const keyDebounceTime = 150; //ms +const sendHotkeyEvent = throttle(keyDebounceTime, (action: GlobalHotkeyAction) => { + console.log('sending hotkey event', action); + window.postMessage({ + type: 'globalHotkey', + action, + }); +}, { noTrailing: true }); + + +/** + * Handles events and returns true if the event was handled. + */ +export function handleHotkeyEvent(e: KeyboardEvent) { + if (e.code === 'KeyK' && e.ctrlKey) { + sendHotkeyEvent('focusPlayerlistFilter'); + return true; + } else if (e.code === 'KeyL' && e.ctrlKey && e.shiftKey && window.txConsts.showAdvanced) { + sendHotkeyEvent('toggleLightMode'); + return true; + } + return false; +} + + +/** + * Event listener for hotkeys with preventDefault. + */ +export function hotkeyEventListener(e: KeyboardEvent) { + if (handleHotkeyEvent(e)) { + e.preventDefault(); + } +} diff --git a/panel/src/pages/Iframe.tsx b/panel/src/pages/Iframe.tsx index 648bcecd5..a0e04f44a 100644 --- a/panel/src/pages/Iframe.tsx +++ b/panel/src/pages/Iframe.tsx @@ -1,14 +1,28 @@ +import { useEffect, useRef } from "react"; +import { hotkeyEventListener } from "@/lib/hotkeyEventListener"; + type Props = { legacyUrl: string; }; export default function Iframe({ legacyUrl }: Props) { + const iframeRef = useRef(null); + //NOTE: if you open adminManager with autofill, the autofill will continue in the searchParams //This is an annoying issue to fix, so #wontfix const searchParams = location.search ?? ''; const hashParams = location.hash ?? ''; + + //Listens to hotkeys in the iframe + useEffect(() => { + if (!iframeRef.current) return; + iframeRef.current.contentWindow?.addEventListener('keydown', hotkeyEventListener); + }, []); + return ( - diff --git a/panel/src/pages/LiveConsole/LiveConsole.tsx b/panel/src/pages/LiveConsole/LiveConsole.tsx index 6dfff571f..3158701c3 100644 --- a/panel/src/pages/LiveConsole/LiveConsole.tsx +++ b/panel/src/pages/LiveConsole/LiveConsole.tsx @@ -19,6 +19,7 @@ import terminalOptions from "./xtermOptions"; import './xtermOverrides.css'; import '@xterm/xterm/css/xterm.css'; import { getSocket, openExternalLink } from '@/lib/utils'; +import { handleHotkeyEvent } from '@/lib/hotkeyEventListener'; const keyDebounceTime = 150; //ms @@ -128,6 +129,8 @@ export default function LiveConsole() { } else if (e.code === 'End') { scrollBottom(); return false; + } else if (handleHotkeyEvent(e)) { + return false; } return true; }); diff --git a/panel/src/pages/LiveConsole/LiveConsoleSearchBar.tsx b/panel/src/pages/LiveConsole/LiveConsoleSearchBar.tsx index f8b7ee314..3afab3381 100644 --- a/panel/src/pages/LiveConsole/LiveConsoleSearchBar.tsx +++ b/panel/src/pages/LiveConsole/LiveConsoleSearchBar.tsx @@ -156,7 +156,6 @@ export default function LiveConsoleSearchBar({ show, setShow, searchAddon }: Liv