171 lines
6.6 KiB
TypeScript
171 lines
6.6 KiB
TypeScript
|
|
import React from 'react';
|
|
import { Playlist, ServerType, PlexServerConnection } from '../types';
|
|
import PlaylistCard from './PlaylistCard';
|
|
import { RefreshCw, Server, Cloud, WifiOff, X } from 'lucide-react';
|
|
import { useLanguage } from '../LanguageContext';
|
|
import OverflowMarquee from './OverflowMarquee';
|
|
|
|
interface ServerPanelProps {
|
|
type: ServerType;
|
|
playlists: Playlist[];
|
|
isLoading: boolean;
|
|
onRefresh: () => void;
|
|
onCancel?: () => void;
|
|
serverInfo?: PlexServerConnection;
|
|
}
|
|
|
|
const ServerPanel: React.FC<ServerPanelProps> = ({ type, playlists, isLoading, onRefresh, onCancel, serverInfo }) => {
|
|
const { t } = useLanguage();
|
|
const isLocal = type === ServerType.LOCAL;
|
|
|
|
let Icon = isLocal ? Server : Cloud;
|
|
let headerColor = isLocal ? 'text-blue-400' : 'text-green-400';
|
|
const borderColor = isLocal ? 'border-blue-500/30' : 'border-green-500/30';
|
|
const bgGradient = isLocal
|
|
? 'bg-gradient-to-br from-gray-800/80 to-gray-900/80'
|
|
: 'bg-gradient-to-bl from-gray-800/80 to-gray-900/80';
|
|
|
|
// Resolve Title and Subtitle Logic
|
|
let displayTitle = '';
|
|
let displaySubtitle: React.ReactNode = null;
|
|
|
|
if (isLocal) {
|
|
displayTitle = t('server.local');
|
|
displaySubtitle = (
|
|
<p className="text-xs text-gray-400 font-medium mt-0.5 md:mt-0 md:ml-0">
|
|
{t('server.playlists', { count: playlists.length })}
|
|
</p>
|
|
);
|
|
} else {
|
|
// Cloud Logic
|
|
if (serverInfo) {
|
|
if (serverInfo.isConnected) {
|
|
displayTitle = serverInfo.name || t('server.cloud');
|
|
displaySubtitle = (
|
|
<div className="flex items-center text-xs text-gray-300 font-medium space-x-1.5 truncate mt-0.5 md:mt-0">
|
|
<span className="text-plex-orange font-semibold min-w-0 max-w-full">
|
|
<span className="block md:hidden truncate">{serverInfo.libraryName}</span>
|
|
<OverflowMarquee className="hidden md:inline-block">
|
|
{serverInfo.libraryName}
|
|
</OverflowMarquee>
|
|
</span>
|
|
<span className="text-gray-600 hidden md:inline">•</span>
|
|
<span className="text-gray-500 font-mono text-[10px] hidden md:inline">{serverInfo.ip}:{serverInfo.port}</span>
|
|
</div>
|
|
);
|
|
} else {
|
|
displayTitle = t('server.notConnected');
|
|
Icon = WifiOff;
|
|
headerColor = 'text-red-400';
|
|
displaySubtitle = (
|
|
<p className="text-xs text-gray-500 font-medium mt-0.5">
|
|
{t('server.connectionFailed')}
|
|
</p>
|
|
);
|
|
}
|
|
} else {
|
|
displayTitle = t('server.cloud');
|
|
displaySubtitle = (
|
|
<p className="text-xs text-gray-500 font-medium mt-0.5">
|
|
{isLoading ? t('server.connecting') : t('server.waiting')}
|
|
</p>
|
|
);
|
|
}
|
|
}
|
|
|
|
// Handle Refresh/Cancel Click
|
|
const handleAction = () => {
|
|
if (isLoading && onCancel) {
|
|
onCancel();
|
|
} else {
|
|
onRefresh();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className={`flex flex-row md:flex-col h-full ${bgGradient} rounded-2xl border ${borderColor} backdrop-blur-xl shadow-xl overflow-hidden transition-all duration-300`}>
|
|
|
|
{/* Header */}
|
|
<div
|
|
className={`
|
|
relative flex-none
|
|
order-last md:order-first
|
|
w-[72px] md:w-full
|
|
h-full md:h-auto md:min-h-[80px]
|
|
flex flex-col md:flex-row items-center justify-between
|
|
py-6 md:py-0 md:px-8
|
|
bg-gray-800/60 border-l md:border-l-0 md:border-b border-white/5
|
|
`}
|
|
>
|
|
|
|
{/* Title Group */}
|
|
<div className="flex flex-col md:flex-row items-center md:space-x-4 overflow-hidden w-full md:w-auto h-full md:h-full md:py-4">
|
|
|
|
{/* Icon Box */}
|
|
<div className={`p-2.5 rounded-xl bg-gray-900/50 border border-white/5 ${headerColor} shadow-inner flex-shrink-0 mb-4 md:mb-0`}>
|
|
<Icon size={22} strokeWidth={2} />
|
|
</div>
|
|
|
|
{/* Text Container */}
|
|
<div className="flex-1 min-w-0 flex flex-col justify-center items-center md:items-start h-full md:h-auto w-full md:w-auto">
|
|
<div className="flex flex-col justify-center w-full md:w-auto [writing-mode:vertical-rl] rotate-180 md:[writing-mode:horizontal-tb] md:rotate-0 items-center md:items-start gap-1 md:gap-0">
|
|
<h2 className="text-sm md:text-lg font-bold text-gray-100 tracking-wide whitespace-nowrap" title={displayTitle}>
|
|
{displayTitle}
|
|
</h2>
|
|
<div className="transform md:translate-y-0">
|
|
{displaySubtitle}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Refresh/Stop Button */}
|
|
<button
|
|
onClick={handleAction}
|
|
className={`flex-shrink-0 p-2.5 rounded-full transition-all active:scale-90 mt-4 md:mt-0 md:ml-4 border border-transparent group relative
|
|
${isLoading
|
|
? 'text-plex-orange bg-plex-orange/10 border-plex-orange/20 hover:bg-red-500/10 hover:border-red-500/30'
|
|
: 'text-gray-400 hover:text-white hover:bg-white/10'
|
|
}
|
|
`}
|
|
title={isLoading ? t('server.cancelRefresh') : t('server.refreshPlaylists')}
|
|
>
|
|
{isLoading ? (
|
|
<div className="relative flex items-center justify-center">
|
|
{/* Outer Spinner */}
|
|
<RefreshCw size={20} strokeWidth={2} className="animate-spin opacity-40 group-hover:opacity-20 transition-opacity" />
|
|
{/* Inner Cancel X */}
|
|
<X size={12} strokeWidth={3} className="absolute text-plex-orange group-hover:text-red-400 transition-colors" />
|
|
</div>
|
|
) : (
|
|
<RefreshCw size={20} strokeWidth={2} />
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
{/* Content List */}
|
|
<div className="flex-1 overflow-y-auto p-3 md:p-5 custom-scrollbar bg-black/20">
|
|
{isLoading && playlists.length === 0 ? (
|
|
<div className="flex flex-col items-center justify-center h-full text-gray-500 space-y-3">
|
|
<RefreshCw size={24} className="animate-spin text-plex-orange/50" />
|
|
<p className="text-xs font-medium tracking-wide uppercase">{t('server.syncing')}</p>
|
|
</div>
|
|
) : playlists.length === 0 ? (
|
|
<div className="flex flex-col items-center justify-center h-full text-gray-500">
|
|
<p className="text-sm">{t('server.noPlaylists')}</p>
|
|
</div>
|
|
) : (
|
|
<div className="space-y-2.5 md:space-y-3">
|
|
{playlists.map((playlist) => (
|
|
<PlaylistCard key={playlist.id} playlist={playlist} />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ServerPanel;
|