201 lines
5.9 KiB
TypeScript
201 lines
5.9 KiB
TypeScript
'use client'
|
|
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu'
|
|
import {
|
|
Maximize2,
|
|
Minimize2,
|
|
Monitor,
|
|
Volume2,
|
|
VolumeX,
|
|
Settings,
|
|
X,
|
|
RefreshCw,
|
|
Signal,
|
|
SignalLow,
|
|
SignalMedium,
|
|
Keyboard,
|
|
MousePointer2,
|
|
Loader2,
|
|
CheckCircle2
|
|
} from 'lucide-react'
|
|
|
|
interface Session {
|
|
id: string
|
|
machine_id: string | null
|
|
machine_name: string | null
|
|
started_at: string
|
|
}
|
|
|
|
type ConnectionState = 'connecting' | 'connected' | 'disconnected' | 'error'
|
|
|
|
interface ViewerToolbarProps {
|
|
session: Session | null
|
|
connectionState: ConnectionState
|
|
isFullscreen: boolean
|
|
quality: 'high' | 'medium' | 'low'
|
|
isMuted: boolean
|
|
onToggleFullscreen: () => void
|
|
onQualityChange: (quality: 'high' | 'medium' | 'low') => void
|
|
onToggleMute: () => void
|
|
onDisconnect: () => void
|
|
onReconnect: () => void
|
|
}
|
|
|
|
export function ViewerToolbar({
|
|
session,
|
|
connectionState,
|
|
isFullscreen,
|
|
quality,
|
|
isMuted,
|
|
onToggleFullscreen,
|
|
onQualityChange,
|
|
onToggleMute,
|
|
onDisconnect,
|
|
onReconnect,
|
|
}: ViewerToolbarProps) {
|
|
const getQualityIcon = () => {
|
|
switch (quality) {
|
|
case 'high': return Signal
|
|
case 'medium': return SignalMedium
|
|
case 'low': return SignalLow
|
|
}
|
|
}
|
|
|
|
const getConnectionBadge = () => {
|
|
switch (connectionState) {
|
|
case 'connecting':
|
|
return (
|
|
<span className="flex items-center gap-2 text-yellow-500">
|
|
<Loader2 className="h-3 w-3 animate-spin" />
|
|
Connecting
|
|
</span>
|
|
)
|
|
case 'connected':
|
|
return (
|
|
<span className="flex items-center gap-2 text-green-500">
|
|
<span className="h-2 w-2 rounded-full bg-green-500" />
|
|
Connected
|
|
</span>
|
|
)
|
|
case 'disconnected':
|
|
return (
|
|
<span className="flex items-center gap-2 text-muted-foreground">
|
|
<span className="h-2 w-2 rounded-full bg-muted-foreground" />
|
|
Disconnected
|
|
</span>
|
|
)
|
|
case 'error':
|
|
return (
|
|
<span className="flex items-center gap-2 text-destructive">
|
|
<span className="h-2 w-2 rounded-full bg-destructive" />
|
|
Error
|
|
</span>
|
|
)
|
|
}
|
|
}
|
|
|
|
const QualityIcon = getQualityIcon()
|
|
|
|
return (
|
|
<div className="flex items-center justify-between h-12 px-4 bg-card/95 backdrop-blur border-b border-border/50">
|
|
{/* Left section */}
|
|
<div className="flex items-center gap-4">
|
|
<div className="flex items-center gap-2">
|
|
<Monitor className="h-4 w-4 text-primary" />
|
|
<span className="font-medium text-sm">
|
|
{session?.machine_name || 'Remote Machine'}
|
|
</span>
|
|
</div>
|
|
<div className="text-sm">
|
|
{getConnectionBadge()}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right section */}
|
|
<div className="flex items-center gap-1">
|
|
{/* Input indicators */}
|
|
<div className="flex items-center gap-2 px-3 border-r border-border/50 mr-2">
|
|
<MousePointer2 className="h-4 w-4 text-muted-foreground" />
|
|
<Keyboard className="h-4 w-4 text-muted-foreground" />
|
|
</div>
|
|
|
|
{/* Quality selector */}
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="ghost" size="icon" className="h-8 w-8">
|
|
<QualityIcon className="h-4 w-4" />
|
|
<span className="sr-only">Quality</span>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuLabel>Stream Quality</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem onClick={() => onQualityChange('high')}>
|
|
<Signal className="mr-2 h-4 w-4" />
|
|
High
|
|
{quality === 'high' && <CheckCircle2 className="ml-auto h-4 w-4 text-primary" />}
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => onQualityChange('medium')}>
|
|
<SignalMedium className="mr-2 h-4 w-4" />
|
|
Medium
|
|
{quality === 'medium' && <CheckCircle2 className="ml-auto h-4 w-4 text-primary" />}
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem onClick={() => onQualityChange('low')}>
|
|
<SignalLow className="mr-2 h-4 w-4" />
|
|
Low
|
|
{quality === 'low' && <CheckCircle2 className="ml-auto h-4 w-4 text-primary" />}
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
|
|
{/* Mute toggle */}
|
|
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={onToggleMute}>
|
|
{isMuted ? (
|
|
<VolumeX className="h-4 w-4" />
|
|
) : (
|
|
<Volume2 className="h-4 w-4" />
|
|
)}
|
|
<span className="sr-only">{isMuted ? 'Unmute' : 'Mute'}</span>
|
|
</Button>
|
|
|
|
{/* Fullscreen toggle */}
|
|
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={onToggleFullscreen}>
|
|
{isFullscreen ? (
|
|
<Minimize2 className="h-4 w-4" />
|
|
) : (
|
|
<Maximize2 className="h-4 w-4" />
|
|
)}
|
|
<span className="sr-only">{isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}</span>
|
|
</Button>
|
|
|
|
{/* Reconnect */}
|
|
{connectionState === 'disconnected' && (
|
|
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={onReconnect}>
|
|
<RefreshCw className="h-4 w-4" />
|
|
<span className="sr-only">Reconnect</span>
|
|
</Button>
|
|
)}
|
|
|
|
{/* Disconnect */}
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-8 w-8 text-destructive hover:text-destructive hover:bg-destructive/10"
|
|
onClick={onDisconnect}
|
|
>
|
|
<X className="h-4 w-4" />
|
|
<span className="sr-only">Disconnect</span>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|