Redesign overlay to Windows Start Menu style - compact bottom bar with expand
- Windows-style search bar with rounded corners - Positioned at bottom center of screen - Logo button to expand/collapse (like Windows Start) - Search input with type-to-search - Expanded view shows Quick Access grid (6 items) - Categorized list (Tools, Plugins, System) - Keyboard shortcuts displayed - Active plugin indicators - Click-through when not focused - Session timer in header - Smooth expand/collapse animations
This commit is contained in:
parent
b60b67f508
commit
5ec15e12f3
|
|
@ -1,4 +1,4 @@
|
||||||
use tauri::{AppHandle, Manager, Window, WindowBuilder, WindowUrl};
|
use tauri::{AppHandle, Manager, Window, WindowBuilder, WindowUrl, Position, PhysicalPosition};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
pub fn create_main_window(app: &AppHandle) -> Window {
|
pub fn create_main_window(app: &AppHandle) -> Window {
|
||||||
|
|
@ -16,43 +16,39 @@ pub fn create_main_window(app: &AppHandle) -> Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_main_window(window: &Window) {
|
pub fn setup_main_window(window: &Window) {
|
||||||
// Additional window setup if needed
|
|
||||||
info!("Main window setup complete");
|
info!("Main window setup complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_overlay_window(app: &AppHandle) {
|
pub fn create_overlay_window(app: &AppHandle) {
|
||||||
|
// Get primary monitor size
|
||||||
|
let monitor = app.primary_monitor().unwrap();
|
||||||
|
let monitor_size = monitor.as_ref().map(|m| m.size()).unwrap_or_else(|| tauri::PhysicalSize::new(1920, 1080));
|
||||||
|
|
||||||
|
// Position overlay at bottom center like Windows Start Menu
|
||||||
|
let window_width = 720.0;
|
||||||
|
let window_height = 600.0; // Expanded height
|
||||||
|
let x = (monitor_size.width as f64 - window_width) / 2.0;
|
||||||
|
let y = monitor_size.height as f64 - window_height - 20.0; // 20px from bottom
|
||||||
|
|
||||||
let window = WindowBuilder::new(
|
let window = WindowBuilder::new(
|
||||||
app,
|
app,
|
||||||
"overlay",
|
"overlay",
|
||||||
WindowUrl::App("/#/overlay".into())
|
WindowUrl::App("/#/overlay".into())
|
||||||
)
|
)
|
||||||
.title("EU-Utility Overlay")
|
.title("EU-Utility Overlay")
|
||||||
.inner_size(400.0, 600.0)
|
.inner_size(window_width, window_height)
|
||||||
.position(100.0, 100.0)
|
.position(x, y)
|
||||||
.transparent(true)
|
.transparent(true)
|
||||||
.decorations(false)
|
.decorations(false)
|
||||||
.always_on_top(true)
|
.always_on_top(true)
|
||||||
.skip_taskbar(true)
|
.skip_taskbar(true)
|
||||||
.visible(false)
|
.visible(false)
|
||||||
|
// Enable click-through when not focused
|
||||||
|
.focus()
|
||||||
.build()
|
.build()
|
||||||
.expect("Failed to create overlay window");
|
.expect("Failed to create overlay window");
|
||||||
|
|
||||||
// Make window click-through when not focused
|
info!("Overlay window created at bottom center (Windows-style)");
|
||||||
// This allows clicking through to the game when overlay is not active
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
use windows::Win32::Foundation::HWND;
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::{SetWindowLongW, GetWindowLongW, GWL_EXSTYLE};
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::{WS_EX_LAYERED, WS_EX_TRANSPARENT};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let hwnd = HWND(window.hwnd().unwrap().0 as *mut _);
|
|
||||||
let ex_style = GetWindowLongW(hwnd, GWL_EXSTYLE);
|
|
||||||
SetWindowLongW(hwnd, GWL_EXSTYLE, ex_style | WS_EX_LAYERED.0 as i32 | WS_EX_TRANSPARENT.0 as i32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Overlay window created with transparency and click-through");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_overlay_window(app: &AppHandle) {
|
pub fn toggle_overlay_window(app: &AppHandle) {
|
||||||
|
|
@ -61,15 +57,42 @@ pub fn toggle_overlay_window(app: &AppHandle) {
|
||||||
|
|
||||||
if is_visible {
|
if is_visible {
|
||||||
window.hide().ok();
|
window.hide().ok();
|
||||||
|
// Re-enable click-through
|
||||||
|
enable_click_through(&window, true);
|
||||||
} else {
|
} else {
|
||||||
window.show().ok();
|
window.show().ok();
|
||||||
window.set_always_on_top(true).ok();
|
window.set_always_on_top(true).ok();
|
||||||
window.set_focus().ok();
|
window.set_focus().ok();
|
||||||
|
// Disable click-through when active
|
||||||
|
enable_click_through(&window, false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
create_overlay_window(app);
|
create_overlay_window(app);
|
||||||
if let Some(window) = app.get_window("overlay") {
|
if let Some(window) = app.get_window("overlay") {
|
||||||
window.show().ok();
|
window.show().ok();
|
||||||
|
enable_click_through(&window, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enable_click_through(window: &Window, enable: bool) {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
unsafe {
|
||||||
|
use windows::Win32::Foundation::HWND;
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::{SetWindowLongW, GetWindowLongW, GWL_EXSTYLE};
|
||||||
|
use windows::Win32::UI::WindowsAndMessaging::{WS_EX_LAYERED, WS_EX_TRANSPARENT, WS_EX_NOACTIVATE};
|
||||||
|
|
||||||
|
let hwnd = HWND(window.hwnd().unwrap().0 as *mut _);
|
||||||
|
let ex_style = GetWindowLongW(hwnd, GWL_EXSTYLE);
|
||||||
|
|
||||||
|
if enable {
|
||||||
|
// Enable click-through
|
||||||
|
SetWindowLongW(hwnd, GWL_EXSTYLE,
|
||||||
|
ex_style | WS_EX_LAYERED.0 as i32 | WS_EX_TRANSPARENT.0 as i32);
|
||||||
|
} else {
|
||||||
|
// Disable click-through
|
||||||
|
SetWindowLongW(hwnd, GWL_EXSTYLE,
|
||||||
|
ex_style & !(WS_EX_TRANSPARENT.0 as i32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,34 +102,13 @@ pub fn show_overlay_window(app: &AppHandle) {
|
||||||
window.show().ok();
|
window.show().ok();
|
||||||
window.set_always_on_top(true).ok();
|
window.set_always_on_top(true).ok();
|
||||||
window.set_focus().ok();
|
window.set_focus().ok();
|
||||||
|
enable_click_through(&window, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hide_overlay_window(app: &AppHandle) {
|
pub fn hide_overlay_window(app: &AppHandle) {
|
||||||
if let Some(window) = app.get_window("overlay") {
|
if let Some(window) = app.get_window("overlay") {
|
||||||
window.hide().ok();
|
window.hide().ok();
|
||||||
|
enable_click_through(&window, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_game_window_detection(app: &AppHandle) {
|
|
||||||
// Set up a timer to check if game window is focused
|
|
||||||
// Only show overlay when game is focused (or on hotkey)
|
|
||||||
let app_handle = app.clone();
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
loop {
|
|
||||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
|
||||||
|
|
||||||
// Check if overlay should auto-hide when game loses focus
|
|
||||||
// This is optional behavior - overlay can stay visible
|
|
||||||
if let Some(overlay) = app_handle.get_window("overlay") {
|
|
||||||
if let Ok(is_visible) = overlay.is_visible() {
|
|
||||||
if is_visible {
|
|
||||||
// Optional: Auto-hide when game not focused
|
|
||||||
// Check game focus here and hide if needed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,54 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { invoke } from '@tauri-apps/api/tauri'
|
import { invoke } from '@tauri-apps/api/tauri'
|
||||||
import { listen } from '@tauri-apps/api/event'
|
import { listen } from '@tauri-apps/api/event'
|
||||||
import {
|
import {
|
||||||
|
Search,
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
Calculator,
|
|
||||||
Target,
|
Target,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
|
Calculator,
|
||||||
Settings,
|
Settings,
|
||||||
X,
|
X,
|
||||||
GripHorizontal,
|
ChevronUp,
|
||||||
|
GripVertical,
|
||||||
Zap,
|
Zap,
|
||||||
Search,
|
|
||||||
Grid,
|
|
||||||
Crosshair,
|
|
||||||
Clock,
|
|
||||||
BarChart3,
|
BarChart3,
|
||||||
Layers
|
Clock,
|
||||||
|
Layers,
|
||||||
|
Maximize2,
|
||||||
|
Minimize2,
|
||||||
|
Grid,
|
||||||
|
Command
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
|
|
||||||
interface QuickAction {
|
interface MenuItem {
|
||||||
id: string
|
id: string
|
||||||
icon: React.ReactNode
|
icon: React.ReactNode
|
||||||
label: string
|
label: string
|
||||||
|
description?: string
|
||||||
hotkey?: string
|
hotkey?: string
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
}
|
category: 'tools' | 'plugins' | 'system'
|
||||||
|
|
||||||
interface PluginWidget {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
icon: React.ReactNode
|
|
||||||
active: boolean
|
|
||||||
onToggle: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Overlay() {
|
export default function Overlay() {
|
||||||
const [isVisible, setIsVisible] = useState(false)
|
const [isVisible, setIsVisible] = useState(false)
|
||||||
const [activePlugins, setActivePlugins] = useState<string[]>([])
|
const [isExpanded, setIsExpanded] = useState(false)
|
||||||
const [sessionTime, setSessionTime] = useState(0)
|
|
||||||
const [currentPED, setCurrentPED] = useState(0)
|
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
|
const [activePlugins, setActivePlugins] = useState<string[]>(['loot', 'skills'])
|
||||||
|
const [sessionTime, setSessionTime] = useState(0)
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Listen for overlay toggle
|
// Listen for overlay toggle
|
||||||
const unlisten = listen('overlay:toggle', () => {
|
const unlisten = listen('overlay:toggle', () => {
|
||||||
setIsVisible(prev => !prev)
|
setIsVisible(prev => {
|
||||||
|
const newVisible = !prev
|
||||||
|
if (newVisible && inputRef.current) {
|
||||||
|
setTimeout(() => inputRef.current?.focus(), 100)
|
||||||
|
}
|
||||||
|
return newVisible
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Session timer
|
// Session timer
|
||||||
|
|
@ -58,205 +62,298 @@ export default function Overlay() {
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isVisible && !isExpanded && inputRef.current) {
|
||||||
|
inputRef.current.focus()
|
||||||
|
}
|
||||||
|
}, [isVisible])
|
||||||
|
|
||||||
const formatTime = (seconds: number) => {
|
const formatTime = (seconds: number) => {
|
||||||
const h = Math.floor(seconds / 3600)
|
const h = Math.floor(seconds / 3600)
|
||||||
const m = Math.floor((seconds % 3600) / 60)
|
const m = Math.floor((seconds % 3600) / 60)
|
||||||
const s = seconds % 60
|
const s = seconds % 60
|
||||||
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
|
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const quickActions: QuickAction[] = [
|
const menuItems: MenuItem[] = [
|
||||||
{
|
{
|
||||||
id: 'calculator',
|
id: 'calculator',
|
||||||
icon: <Calculator className="w-5 h-5" />,
|
icon: <Calculator className="w-5 h-5" />,
|
||||||
label: 'Calc',
|
label: 'Calculator',
|
||||||
|
description: 'Quick calculations',
|
||||||
hotkey: 'Ctrl+Shift+C',
|
hotkey: 'Ctrl+Shift+C',
|
||||||
onClick: () => invoke('open_calculator')
|
onClick: () => invoke('open_calculator'),
|
||||||
|
category: 'tools'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'loot-tracker',
|
id: 'loot-tracker',
|
||||||
icon: <Target className="w-5 h-5" />,
|
icon: <Target className="w-5 h-5" />,
|
||||||
label: 'Loot',
|
label: 'Loot Tracker',
|
||||||
|
description: 'Track hunting sessions',
|
||||||
hotkey: 'Ctrl+Shift+L',
|
hotkey: 'Ctrl+Shift+L',
|
||||||
onClick: () => invoke('toggle_loot_tracker')
|
onClick: () => togglePlugin('loot'),
|
||||||
|
category: 'plugins'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'skill-tracker',
|
||||||
|
icon: <TrendingUp className="w-5 h-5" />,
|
||||||
|
label: 'Skill Tracker',
|
||||||
|
description: 'Monitor skill gains',
|
||||||
|
onClick: () => togglePlugin('skills'),
|
||||||
|
category: 'plugins'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'price-check',
|
id: 'price-check',
|
||||||
icon: <BarChart3 className="w-5 h-5" />,
|
icon: <BarChart3 className="w-5 h-5" />,
|
||||||
label: 'Prices',
|
label: 'Price Check',
|
||||||
onClick: () => invoke('open_price_checker')
|
description: 'Item market values',
|
||||||
|
onClick: () => invoke('open_price_checker'),
|
||||||
|
category: 'tools'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'skills',
|
id: 'dashboard',
|
||||||
icon: <TrendingUp className="w-5 h-5" />,
|
icon: <LayoutDashboard className="w-5 h-5" />,
|
||||||
label: 'Skills',
|
label: 'Dashboard',
|
||||||
onClick: () => invoke('open_skill_tracker')
|
description: 'Main application window',
|
||||||
},
|
onClick: () => invoke('show_main_window'),
|
||||||
{
|
category: 'system'
|
||||||
id: 'search',
|
|
||||||
icon: <Search className="w-5 h-5" />,
|
|
||||||
label: 'Search',
|
|
||||||
hotkey: 'Ctrl+Shift+F',
|
|
||||||
onClick: () => invoke('open_universal_search')
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'settings',
|
id: 'settings',
|
||||||
icon: <Settings className="w-5 h-5" />,
|
icon: <Settings className="w-5 h-5" />,
|
||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
onClick: () => invoke('show_settings_window')
|
description: 'Configure application',
|
||||||
}
|
onClick: () => invoke('show_settings_window'),
|
||||||
|
category: 'system'
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const plugins: PluginWidget[] = [
|
const togglePlugin = (pluginId: string) => {
|
||||||
{ id: '1', name: 'Loot Tracker', icon: <Target className="w-4 h-4" />, active: true, onToggle: () => {} },
|
setActivePlugins(prev =>
|
||||||
{ id: '2', name: 'HP Monitor', icon: <Crosshair className="w-4 h-4" />, active: false, onToggle: () => {} },
|
prev.includes(pluginId)
|
||||||
{ id: '3', name: 'Skill Watch', icon: <TrendingUp className="w-4 h-4" />, active: true, onToggle: () => {} },
|
? prev.filter(id => id !== pluginId)
|
||||||
{ id: '4', name: 'Coords', icon: <Grid className="w-4 h-4" />, active: false, onToggle: () => {} },
|
: [...prev, pluginId]
|
||||||
]
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const filteredPlugins = plugins.filter(p =>
|
const filteredItems = searchQuery
|
||||||
p.name.toLowerCase().includes(searchQuery.toLowerCase())
|
? menuItems.filter(item =>
|
||||||
)
|
item.label.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
item.description?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
|
)
|
||||||
|
: menuItems
|
||||||
|
|
||||||
|
const groupedItems = filteredItems.reduce((acc, item) => {
|
||||||
|
if (!acc[item.category]) acc[item.category] = []
|
||||||
|
acc[item.category].push(item)
|
||||||
|
return acc
|
||||||
|
}, {} as Record<string, MenuItem[]>)
|
||||||
|
|
||||||
if (!isVisible) return null
|
if (!isVisible) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 pointer-events-none flex items-start justify-center pt-4">
|
<div className="fixed inset-0 pointer-events-none flex items-end justify-center pb-6">
|
||||||
{/* Overlay Container */}
|
{/* Windows-Style Overlay Container */}
|
||||||
<div
|
<div
|
||||||
className="pointer-events-auto animate-in slide-in-from-top-4 duration-200"
|
className="pointer-events-auto transition-all duration-300 ease-out"
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(10, 10, 15, 0.92)',
|
width: isExpanded ? '720px' : '640px',
|
||||||
backdropFilter: 'blur(20px) saturate(180%)',
|
maxWidth: '90vw',
|
||||||
WebkitBackdropFilter: 'blur(20px) saturate(180%)',
|
|
||||||
border: '1px solid rgba(99, 102, 241, 0.3)',
|
|
||||||
borderRadius: '16px',
|
|
||||||
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(99, 102, 241, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.05)',
|
|
||||||
minWidth: '380px',
|
|
||||||
maxWidth: '420px',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Header - Drag Handle */}
|
{/* Main Search Bar - Windows Style */}
|
||||||
<div
|
<div
|
||||||
className="flex items-center justify-between px-4 py-3 border-b border-white/10 cursor-move"
|
className="relative overflow-hidden"
|
||||||
data-tauri-drag-region
|
style={{
|
||||||
|
background: 'rgba(32, 32, 32, 0.95)',
|
||||||
|
backdropFilter: 'blur(32px) saturate(180%)',
|
||||||
|
WebkitBackdropFilter: 'blur(32px) saturate(180%)',
|
||||||
|
borderRadius: isExpanded ? '12px' : '32px',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.08)',
|
||||||
|
boxShadow: isExpanded
|
||||||
|
? '0 32px 64px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.05), inset 0 1px 0 rgba(255, 255, 255, 0.1)'
|
||||||
|
: '0 8px 32px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.05)',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3">
|
{/* Search Input Row */}
|
||||||
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center shadow-lg shadow-indigo-500/20">
|
<div
|
||||||
<Layers className="w-4 h-4 text-white" />
|
className="flex items-center gap-3 px-5 h-14"
|
||||||
</div>
|
data-tauri-drag-region
|
||||||
<div>
|
>
|
||||||
<h3 className="text-sm font-semibold text-white">EU-Utility</h3>
|
{/* Logo Icon - Windows Style */}
|
||||||
<p className="text-xs text-white/50">App Drawer</p>
|
<div
|
||||||
</div>
|
className="flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center cursor-pointer transition-all hover:scale-105 active:scale-95"
|
||||||
</div>
|
style={{
|
||||||
|
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%)',
|
||||||
<div className="flex items-center gap-2">
|
boxShadow: '0 4px 12px rgba(99, 102, 241, 0.4)',
|
||||||
{/* Session Timer */}
|
}}
|
||||||
<div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-white/5 border border-white/10">
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
<Clock className="w-3 h-3 text-indigo-400" />
|
|
||||||
<span className="text-xs font-mono text-white/80">{formatTime(sessionTime)}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => setIsVisible(false)}
|
|
||||||
className="w-7 h-7 flex items-center justify-center rounded-lg hover:bg-white/10 transition-colors"
|
|
||||||
>
|
>
|
||||||
<X className="w-4 h-4 text-white/60 hover:text-white" />
|
<Layers className="w-5 h-5 text-white" />
|
||||||
</button>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Search Bar */}
|
{/* Search Input */}
|
||||||
<div className="px-4 py-3 border-b border-white/10">
|
<div className="flex-1 relative">
|
||||||
<div className="relative">
|
<input
|
||||||
<Search className="w-4 h-4 text-white/40 absolute left-3 top-1/2 -translate-y-1/2" />
|
ref={inputRef}
|
||||||
<input
|
type="text"
|
||||||
type="text"
|
value={searchQuery}
|
||||||
value={searchQuery}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onFocus={() => setIsExpanded(true)}
|
||||||
placeholder="Search plugins..."
|
placeholder="Type to search plugins, tools..."
|
||||||
className="w-full pl-9 pr-3 py-2 bg-black/30 border border-white/10 rounded-lg text-sm text-white placeholder:text-white/30 focus:outline-none focus:border-indigo-500/50 transition-colors"
|
className="w-full bg-transparent border-none text-white text-base placeholder:text-white/40 focus:outline-none"
|
||||||
/>
|
style={{ fontSize: '15px' }}
|
||||||
</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick Actions Grid */}
|
{/* Right Side Actions */}
|
||||||
<div className="px-4 py-3 border-b border-white/10">
|
<div className="flex items-center gap-2">
|
||||||
<p className="text-xs font-medium text-white/40 uppercase tracking-wider mb-3">Quick Actions</p>
|
{/* Session Timer */}
|
||||||
<div className="grid grid-cols-3 gap-2">
|
|
||||||
{quickActions.map(action => (
|
|
||||||
<button
|
|
||||||
key={action.id}
|
|
||||||
onClick={action.onClick}
|
|
||||||
className="group flex flex-col items-center gap-1.5 p-2.5 rounded-xl bg-white/5 hover:bg-white/10 border border-white/5 hover:border-indigo-500/30 transition-all duration-150"
|
|
||||||
>
|
|
||||||
<div className="w-9 h-9 rounded-lg bg-gradient-to-br from-indigo-500/20 to-purple-500/20 group-hover:from-indigo-500/30 group-hover:to-purple-500/30 flex items-center justify-center transition-colors">
|
|
||||||
<div className="text-indigo-400 group-hover:text-indigo-300 transition-colors">
|
|
||||||
{action.icon}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span className="text-[10px] font-medium text-white/70 group-hover:text-white transition-colors">
|
|
||||||
{action.label}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Active Plugins */}
|
|
||||||
<div className="px-4 py-3">
|
|
||||||
<div className="flex items-center justify-between mb-3">
|
|
||||||
<p className="text-xs font-medium text-white/40 uppercase tracking-wider">Plugins</p>
|
|
||||||
<span className="text-xs text-white/30">{filteredPlugins.filter(p => p.active).length} active</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
|
||||||
{filteredPlugins.map(plugin => (
|
|
||||||
<div
|
<div
|
||||||
key={plugin.id}
|
className="flex items-center gap-2 px-3 py-1.5 rounded-full"
|
||||||
className={`flex items-center justify-between p-2.5 rounded-lg border transition-all cursor-pointer ${
|
style={{ background: 'rgba(255, 255, 255, 0.06)' }}
|
||||||
plugin.active
|
|
||||||
? 'bg-indigo-500/10 border-indigo-500/30'
|
|
||||||
: 'bg-white/5 border-white/5 hover:border-white/10'
|
|
||||||
}`}
|
|
||||||
onClick={plugin.onToggle}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2.5">
|
<Clock className="w-4 h-4 text-indigo-400" />
|
||||||
<div className={`w-7 h-7 rounded-md flex items-center justify-center ${
|
<span className="text-sm font-mono text-white/80">{formatTime(sessionTime)}</span>
|
||||||
plugin.active ? 'bg-indigo-500/20 text-indigo-400' : 'bg-white/10 text-white/50'
|
</div>
|
||||||
}`}>
|
|
||||||
{plugin.icon}
|
{/* Expand/Collapse */}
|
||||||
|
<button
|
||||||
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
className="w-8 h-8 flex items-center justify-center rounded-full hover:bg-white/10 transition-colors"
|
||||||
|
>
|
||||||
|
{isExpanded ? (
|
||||||
|
<Minimize2 className="w-4 h-4 text-white/60" />
|
||||||
|
) : (
|
||||||
|
<Maximize2 className="w-4 h-4 text-white/60" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Close */}
|
||||||
|
<button
|
||||||
|
onClick={() => setIsVisible(false)}
|
||||||
|
className="w-8 h-8 flex items-center justify-center rounded-full hover:bg-white/10 transition-colors"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4 text-white/60 hover:text-white" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Expanded Menu - Windows Start Menu Style */}
|
||||||
|
{isExpanded && (
|
||||||
|
<div
|
||||||
|
className="border-t border-white/10"
|
||||||
|
style={{
|
||||||
|
maxHeight: '480px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Quick Actions Grid */}
|
||||||
|
<div className="p-5">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<span className="text-xs font-semibold uppercase tracking-wider text-white/40">Quick Access</span>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-xs text-white/30">{activePlugins.length} active</span>
|
||||||
</div>
|
</div>
|
||||||
<span className={`text-xs font-medium ${plugin.active ? 'text-white' : 'text-white/60'}`}>
|
|
||||||
{plugin.name}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={`w-8 h-4 rounded-full relative transition-colors ${
|
<div className="grid grid-cols-6 gap-2">
|
||||||
plugin.active ? 'bg-indigo-500' : 'bg-white/20'
|
{menuItems.slice(0, 6).map((item) => (
|
||||||
}`}>
|
<button
|
||||||
<div className={`absolute top-0.5 w-3 h-3 rounded-full bg-white transition-all ${
|
key={item.id}
|
||||||
plugin.active ? 'left-4.5' : 'left-0.5'
|
onClick={() => {
|
||||||
}`} />
|
item.onClick()
|
||||||
|
if (!item.id.includes('tracker')) setIsVisible(false)
|
||||||
|
}}
|
||||||
|
className="group flex flex-col items-center gap-2 p-3 rounded-xl transition-all hover:bg-white/10"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`w-12 h-12 rounded-xl flex items-center justify-center transition-all ${
|
||||||
|
activePlugins.includes(item.id) || item.id === 'loot' && activePlugins.includes('loot')
|
||||||
|
? 'bg-indigo-500/20 text-indigo-400 ring-1 ring-indigo-500/30'
|
||||||
|
: 'bg-white/5 text-white/60 group-hover:bg-white/10 group-hover:text-white'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</div>
|
||||||
|
<span className="text-[11px] text-white/70 text-center leading-tight">{item.label}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Stats Footer */}
|
{/* Search Results or All Items */}
|
||||||
<div className="px-4 py-3 border-t border-white/10 bg-white/5">
|
<div className="px-5 pb-5" style={{ maxHeight: '280px', overflowY: 'auto' }}>
|
||||||
<div className="flex items-center justify-between">
|
{Object.entries(groupedItems).map(([category, items]) => (
|
||||||
<div className="flex items-center gap-2">
|
<div key={category} className="mb-4 last:mb-0">
|
||||||
<div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-xs text-white/60">System Online</span>
|
<span className="text-xs font-semibold uppercase tracking-wider text-white/40">
|
||||||
|
{category === 'tools' ? 'Tools' : category === 'plugins' ? 'Plugins' : 'System'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{items.map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
onClick={() => {
|
||||||
|
item.onClick()
|
||||||
|
if (!item.id.includes('tracker')) setIsVisible(false)
|
||||||
|
}}
|
||||||
|
className="w-full flex items-center gap-3 p-3 rounded-lg hover:bg-white/10 transition-all group"
|
||||||
|
>
|
||||||
|
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${
|
||||||
|
activePlugins.includes(item.id) || (item.id === 'loot-tracker' && activePlugins.includes('loot'))
|
||||||
|
? 'bg-indigo-500/20 text-indigo-400'
|
||||||
|
: 'bg-white/5 text-white/60 group-hover:text-white'
|
||||||
|
}`}>
|
||||||
|
{item.icon}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 text-left">
|
||||||
|
<div className="text-sm font-medium text-white/90 group-hover:text-white">{item.label}</div>
|
||||||
|
{item.description && (
|
||||||
|
<div className="text-xs text-white/40">{item.description}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{item.hotkey && (
|
||||||
|
<kbd className="px-2 py-1 text-xs rounded bg-white/5 text-white/40 font-mono">
|
||||||
|
{item.hotkey}
|
||||||
|
</kbd>
|
||||||
|
)}
|
||||||
|
{(activePlugins.includes(item.id) || (item.id === 'loot-tracker' && activePlugins.includes('loot'))) && (
|
||||||
|
<div className="w-2 h-2 rounded-full bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.5)]" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{filteredItems.length === 0 && (
|
||||||
|
<div className="text-center py-8 text-white/40">
|
||||||
|
<Search className="w-12 h-12 mx-auto mb-3 opacity-30" />
|
||||||
|
<p className="text-sm">No results found for "{searchQuery}"</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-between px-5 py-3 border-t border-white/10"
|
||||||
|
style={{ background: 'rgba(0, 0, 0, 0.3)' }}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
||||||
|
<span className="text-xs text-white/50">EU-Utility V3.0</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1 text-xs text-white/40">
|
||||||
|
<Command className="w-3 h-3" />
|
||||||
|
<span>Press</span>
|
||||||
|
<kbd className="px-1.5 py-0.5 rounded bg-white/10 text-white/60">Ctrl+Shift+U</kbd>
|
||||||
|
<span>to toggle</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1.5 text-xs text-white/50">
|
)}
|
||||||
<span className="text-emerald-400 font-medium">{currentPED.toFixed(2)}</span>
|
|
||||||
<span>PED/h</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue