Files
linux-mirror-browser/src/components/CommandModal.tsx

261 lines
8.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useState, useEffect } from 'react';
import { Config } from '@/types';
interface CommandModalProps {
config: Config;
isOpen: boolean;
onClose: () => void;
}
export function CommandModal({ config, isOpen, onClose }: CommandModalProps) {
const [selectedDistro, setSelectedDistro] = useState('');
const [command, setCommand] = useState('');
const [loading, setLoading] = useState(false);
const [copied, setCopied] = useState(false);
// 重置状态当模态框关闭时
useEffect(() => {
if (!isOpen) {
setSelectedDistro('');
setCommand('');
setCopied(false);
}
}, [isOpen]);
const distros = config.linuxCommands.map(distro => {
const dirConfig = config.directories.find(d => d.name === distro);
return {
name: distro,
description: dirConfig?.description || distro,
icon: dirConfig?.icon || 'fab fa-linux',
color: dirConfig?.color || '#000000'
};
});
const generateCommand = async () => {
if (!selectedDistro) return;
setLoading(true);
try {
const response = await fetch('/api/command', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ distro: selectedDistro }),
});
const data = await response.json();
if (data.success) {
setCommand(data.data.command);
} else {
console.error('Failed to generate command:', data.error);
}
} catch (error) {
console.error('Error generating command:', error);
} finally {
setLoading(false);
}
};
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(command);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (error) {
console.error('Failed to copy to clipboard:', error);
}
};
useEffect(() => {
if (selectedDistro) {
generateCommand();
} else {
setCommand('');
}
}, [selectedDistro]);
// 按ESC键关闭模态框
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape' && isOpen) {
onClose();
}
};
if (isOpen) {
document.addEventListener('keydown', handleEscape);
document.body.style.overflow = 'hidden'; // 防止背景滚动
}
return () => {
document.removeEventListener('keydown', handleEscape);
document.body.style.overflow = 'unset'; // 恢复滚动
};
}, [isOpen, onClose]);
if (!isOpen) {
return null;
}
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center"
onClick={onClose}
>
{/* 背景遮罩 */}
<div className="absolute inset-0 bg-black bg-opacity-50 transition-opacity"></div>
{/* 模态框内容 */}
<div
className="relative bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto"
onClick={(e) => e.stopPropagation()} // 防止点击内容区域时关闭
>
<div className="p-6">
{/* 标题和关闭按钮 */}
<div className="flex items-center justify-between mb-4">
<h3 className="text-xl font-bold text-gray-800 dark:text-gray-200">
Linux
</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors"
title="关闭"
>
<i className="fas fa-times text-xl"></i>
</button>
</div>
{/* 发行版选择 */}
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Linux
</label>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{distros.map((distro) => (
<button
key={distro.name}
onClick={() => setSelectedDistro(distro.name)}
className={`p-3 rounded-lg border-2 transition-all ${
selectedDistro === distro.name
? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20'
: 'border-gray-200 dark:border-gray-600 hover:border-gray-300 dark:hover:border-gray-500'
}`}
>
<div className="text-center">
<i
className={`${distro.icon} text-2xl mb-1`}
style={{ color: distro.color }}
></i>
<div className="text-sm font-medium text-gray-800 dark:text-gray-200">
{distro.name.charAt(0).toUpperCase() + distro.name.slice(1)}
</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{distro.description}
</div>
</div>
</button>
))}
</div>
</div>
{/* 命令生成结果 */}
{selectedDistro && (
<div className="mb-6">
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
</label>
<button
onClick={copyToClipboard}
className="px-3 py-1 text-sm bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors"
disabled={loading || !command}
>
{copied ? (
<>
<i className="fas fa-check mr-1"></i>
</>
) : (
<>
<i className="fas fa-copy mr-1"></i>
</>
)}
</button>
</div>
<div className="relative">
<div className="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm overflow-x-auto">
{loading ? (
<div className="flex items-center space-x-2">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-green-400"></div>
<span>...</span>
</div>
) : command ? (
<pre className="whitespace-pre-wrap">{command}</pre>
) : (
<span></span>
)}
</div>
</div>
</div>
)}
{/* 使用说明 */}
{selectedDistro && command && (
<div className="mb-6">
<h4 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
使
</h4>
<ol className="list-decimal list-inside space-y-1 text-sm text-gray-600 dark:text-gray-400">
<li></li>
<li></li>
<li>
<code className="bg-gray-100 dark:bg-gray-700 px-1 rounded">
{selectedDistro === 'ubuntu' || selectedDistro === 'debian'
? 'sudo apt update'
: selectedDistro === 'centos' || selectedDistro === 'fedora'
? 'sudo dnf update'
: 'sudo pacman -Syu'
}
</code>
</li>
<li></li>
</ol>
</div>
)}
{/* 注意事项 */}
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
<div className="flex items-start space-x-2">
<i className="fas fa-exclamation-triangle text-yellow-600 dark:text-yellow-400 mt-1"></i>
<div className="text-sm text-yellow-800 dark:text-yellow-200">
<p className="font-medium mb-1"></p>
<ul className="list-disc list-inside space-y-1">
<li></li>
<li> Linux 使</li>
<li> DNS </li>
<li></li>
</ul>
</div>
</div>
</div>
{/* 底部按钮 */}
<div className="flex justify-end pt-4 border-t border-gray-200 dark:border-gray-700">
<button
onClick={onClose}
className="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors"
>
</button>
</div>
</div>
</div>
</div>
);
}