translate comment of frontend/components
This commit is contained in:
@@ -18,7 +18,7 @@ const ClipboardApp = () => {
|
||||
|
||||
const [retrieveRoomIdInput, setRetrieveRoomIdInput] = useState("");
|
||||
const [activeTab, setActiveTab] = useState<"send" | "retrieve">("send");
|
||||
const retrieveJoinRoomBtnRef = useRef<HTMLButtonElement>(null); //接收方--加入房间按钮ref
|
||||
const retrieveJoinRoomBtnRef = useRef<HTMLButtonElement>(null); // Ref for the receiver's "Join Room" button
|
||||
|
||||
const { messages, isLoadingMessages } = usePageSetup({
|
||||
setRetrieveRoomId: setRetrieveRoomIdInput,
|
||||
|
||||
@@ -11,21 +11,20 @@ import { getDictionary } from "@/lib/dictionary";
|
||||
import { useLocale } from "@/hooks/useLocale";
|
||||
import type { Messages } from "@/types/messages";
|
||||
|
||||
function formatFolderDis(
|
||||
template: string,
|
||||
num: number,
|
||||
size: string
|
||||
) {
|
||||
return template.replace('{num}', num.toString()).replace('{size}', size);
|
||||
function formatFolderDis(template: string, num: number, size: string) {
|
||||
return template.replace("{num}", num.toString()).replace("{size}", size);
|
||||
}
|
||||
|
||||
function formatFolderTips(
|
||||
template: string,
|
||||
name: string,
|
||||
num: number,
|
||||
template: string,
|
||||
name: string,
|
||||
num: number,
|
||||
size: string
|
||||
) {
|
||||
return template.replace('{name}', name).replace('{num}', num.toString()).replace('{size}', size);
|
||||
return template
|
||||
.replace("{name}", name)
|
||||
.replace("{num}", num.toString())
|
||||
.replace("{size}", size);
|
||||
}
|
||||
|
||||
interface FileListDisplayProps {
|
||||
@@ -37,15 +36,15 @@ interface FileListDisplayProps {
|
||||
};
|
||||
};
|
||||
onDownload?: (item: FileMeta) => void;
|
||||
onRequest?: (item: FileMeta) => void; //请求文件
|
||||
onRequest?: (item: FileMeta) => void; // Request file
|
||||
onDelete?: (item: FileMeta) => void;
|
||||
onLocationPick?: () => Promise<boolean>;
|
||||
saveType?: { [fileId: string]: boolean }; //文件是存储在磁盘还是内存
|
||||
saveType?: { [fileId: string]: boolean }; // File stored on disk or in memory
|
||||
largeFileThreshold?: number;
|
||||
}
|
||||
// 添加类型判断辅助函数
|
||||
// Add type guard helper function
|
||||
function isCustomFile(file: FileMeta | CustomFile): file is CustomFile {
|
||||
return "lastModified" in file; // 使用 File 对象特有的属性来判断
|
||||
return "lastModified" in file; // Use a property specific to File objects to check
|
||||
}
|
||||
function isFileMetaArray(
|
||||
files: FileMeta[] | CustomFile[]
|
||||
@@ -68,21 +67,21 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
const [showFinished, setShowFinished] = useState<{
|
||||
[fileId: string]: boolean;
|
||||
}>({});
|
||||
// 添加 ref 来存储上一次的 showFinished 状态
|
||||
// Add a ref to store the previous showFinished state
|
||||
const prevShowFinishedRef = useRef<{ [fileId: string]: boolean }>({});
|
||||
|
||||
const [pickedLocation, setPickedLocation] = useState<boolean>(false); //是否已经选取过保存目录
|
||||
const [needPickLocation, setNeedPickLocation] = useState<boolean>(false); //是否需要选择保存目录--大文件或文件夹或用户主动选择
|
||||
const [pickedLocation, setPickedLocation] = useState<boolean>(false); // Whether a save directory has been selected
|
||||
const [needPickLocation, setNeedPickLocation] = useState<boolean>(false); // Whether a save directory needs to be selected -- for large files, folders, or user choice
|
||||
|
||||
const [folders, setFolders] = useState<FileMeta[]>([]); //将文件中属于 文件夹的 提取出来
|
||||
const [singleFiles, setSingleFiles] = useState<FileMeta[]>([]); //保留单文件,不属于文件夹
|
||||
// 添加当前显示的接收端追踪
|
||||
const [folders, setFolders] = useState<FileMeta[]>([]); // Extract folder items from files
|
||||
const [singleFiles, setSingleFiles] = useState<FileMeta[]>([]); // Keep single files, not part of a folder
|
||||
// Add tracking for currently displayed receiver
|
||||
const [activeTransfers, setActiveTransfers] = useState<{
|
||||
[fileId: string]: string;
|
||||
}>({});
|
||||
//对是否有文件传输状态进行跟踪
|
||||
// Track if any file transfer is in progress
|
||||
const [isAnyFileTransferring, setIsAnyFileTransferring] = useState(false);
|
||||
// 添加下载次数的状态
|
||||
// Add state for download counts
|
||||
const [downloadCounts, setDownloadCounts] = useState<{
|
||||
[fileId: string]: number;
|
||||
}>({});
|
||||
@@ -91,7 +90,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
.then((dict) => setMessages(dict))
|
||||
.catch((error) => console.error("Failed to load messages:", error));
|
||||
}, [locale]);
|
||||
//监控文件传输状态
|
||||
// Monitor file transfer status
|
||||
useEffect(() => {
|
||||
const hasActiveTransfer = Object.values(fileProgresses).some(
|
||||
(fileProgress) =>
|
||||
@@ -103,12 +102,12 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
}, [fileProgresses]);
|
||||
|
||||
useEffect(() => {
|
||||
//分离出单文件和文件夹
|
||||
// Separate single files and folders
|
||||
const tempSingleFiles: FileMeta[] = [];
|
||||
let folders_: { [folderName: string]: FileMeta } = {};
|
||||
let needPick = false;
|
||||
|
||||
// 如果是 CustomFile[] 类型,先转换为 FileMeta[]
|
||||
// If it's a CustomFile[] type, convert it to FileMeta[] first
|
||||
const processedFiles: FileMeta[] = isFileMetaArray(files)
|
||||
? files
|
||||
: files.map((file) => ({
|
||||
@@ -123,10 +122,10 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
for (let file of processedFiles) {
|
||||
if (file.folderName !== "") {
|
||||
folders_[file.folderName] = folders_[file.folderName] || {
|
||||
//如果对象不存在,则初始化
|
||||
// If the object doesn't exist, initialize it
|
||||
name: file.folderName,
|
||||
size: 0,
|
||||
fullName: file.folderName, // 文件夹的 fullName 就是 folderName
|
||||
fullName: file.folderName, // The fullName of a folder is its folderName
|
||||
fileType: "folder",
|
||||
fileId: file.folderName,
|
||||
folderName: file.folderName,
|
||||
@@ -135,7 +134,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
};
|
||||
|
||||
folders_[file.folderName].fileCount =
|
||||
(folders_[file.folderName].fileCount ?? 0) + 1; //如果 fileCount 是 undefined,使用默认值 0
|
||||
(folders_[file.folderName].fileCount ?? 0) + 1; // If fileCount is undefined, use default value 0
|
||||
folders_[file.folderName].size += file.size;
|
||||
folders_[file.folderName].fileNamesDis = folders_[file.folderName]
|
||||
.fileNamesDis
|
||||
@@ -151,7 +150,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
// console.log('Single files before setState:', tempSingleFiles);
|
||||
// console.log('Folders before setState:', Object.values(folders_));
|
||||
|
||||
// 使用函数式更新确保状态正确更新
|
||||
// Use functional updates to ensure the state is updated correctly
|
||||
setSingleFiles((prev) => {
|
||||
// console.log('Previous single files:', prev);
|
||||
// console.log('New single files:', tempSingleFiles);
|
||||
@@ -162,32 +161,32 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
// console.log('New folders:', Object.values(folders_));
|
||||
return [...Object.values(folders_)];
|
||||
});
|
||||
setNeedPickLocation(needPick); //设置是否需要选择保存目录
|
||||
setNeedPickLocation(needPick); // Set whether a save directory needs to be selected
|
||||
}, [files, largeFileThreshold]);
|
||||
|
||||
useEffect(() => {
|
||||
//如果一个文件同时被多个接收端请求,会先显示第一个,等结束后再显示第二个
|
||||
// If a file is requested by multiple receivers simultaneously, the first one will be displayed, then the second after the first finishes.
|
||||
let fileIds = [...singleFiles, ...folders].map((file) => file.fileId);
|
||||
|
||||
fileIds.forEach((fileId) => {
|
||||
const fileProgress = fileProgresses[fileId];
|
||||
if (!fileProgress) return;
|
||||
// 获取当前文件的所有传输进度
|
||||
// Get all transfer progresses for the current file
|
||||
const transfers = Object.entries(fileProgress);
|
||||
// 如果没有活跃传输,选择第一个开始的传输
|
||||
// If there are no active transfers, select the first one that started
|
||||
let newPeerId = "";
|
||||
if (!activeTransfers[fileId] && transfers.length > 0) {
|
||||
newPeerId = transfers[0][0];
|
||||
setActiveTransfers((prev) => ({
|
||||
...prev,
|
||||
[fileId]: newPeerId, // 设置第一个 peerId
|
||||
[fileId]: newPeerId, // Set the first peerId
|
||||
}));
|
||||
}
|
||||
// set是异步操作 直接使用 newPeerId 而不是从 activeTransfers 中读取
|
||||
// set is an async operation, use newPeerId directly instead of reading from activeTransfers
|
||||
const activePeerId = newPeerId || activeTransfers[fileId];
|
||||
// 检查当前活跃传输是否完成
|
||||
// Check if the current active transfer is complete
|
||||
if (activePeerId && fileProgress[activePeerId]?.progress >= 1) {
|
||||
// 当前传输完成,等待2秒后切换到下一个未完成的传输
|
||||
// Current transfer is complete, wait 2 seconds before switching to the next incomplete transfer
|
||||
if (!showFinished[fileId]) {
|
||||
setShowFinished((prev) => ({ ...prev, [fileId]: true }));
|
||||
|
||||
@@ -198,8 +197,8 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
return updated;
|
||||
});
|
||||
|
||||
delete fileProgress[activePeerId]; //需要删掉这个peer的进度,否则下次相同文件被请求进度显示不正常
|
||||
// 找到下一个未完成的传输
|
||||
delete fileProgress[activePeerId]; //need to delete the progress of this peer, otherwise the progress will be displayed abnormally when the same file is requested next time.
|
||||
// Find the next outstanding transfer
|
||||
const nextTransfer = transfers.find(
|
||||
([pid, prog]) =>
|
||||
pid !== activePeerId && prog.progress > 0 && prog.progress < 1
|
||||
@@ -228,7 +227,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
//监控 Finished 从false/null跳变为true这个事件 来触发下载
|
||||
//Monitor the Finished event from false/null to true to trigger the download
|
||||
let files_ = [...singleFiles, ...folders];
|
||||
|
||||
files_.forEach((item: FileMeta) => {
|
||||
@@ -236,32 +235,32 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
const prevShowFinished = prevShowFinishedRef.current[item.fileId];
|
||||
const isSaveToDisk = saveType ? saveType[item.fileId] : false;
|
||||
// console.log(`last:${prevShowFinished} --> cur:${currentShowFinished}`);
|
||||
// 检测 false -> true 的跳变
|
||||
// Detecting false -> true transitions
|
||||
if (!prevShowFinished && currentShowFinished) {
|
||||
if (!isSaveToDisk && onDownload) {
|
||||
onDownload(item);
|
||||
}
|
||||
|
||||
// 增加下载次数
|
||||
// Increase download count
|
||||
setDownloadCounts((prevCounts) => ({
|
||||
...prevCounts,
|
||||
[item.fileId]: (prevCounts[item.fileId] || 0) + 1,
|
||||
}));
|
||||
}
|
||||
|
||||
// 更新上一次的状态
|
||||
// Update the last status
|
||||
prevShowFinishedRef.current[item.fileId] = currentShowFinished;
|
||||
});
|
||||
}, [showFinished, singleFiles, folders, saveType, onDownload]);
|
||||
|
||||
//每一项文件 对应的动作--进度、下载、删除
|
||||
//Actions corresponding to each file - progress, download, delete
|
||||
const renderItemActions = (item: FileMeta) => {
|
||||
const fileProgress = fileProgresses[item.fileId];
|
||||
const activePeerId = activeTransfers[item.fileId];
|
||||
const progress = activePeerId ? fileProgress?.[activePeerId] : null;
|
||||
const showCompletion = showFinished[item.fileId];
|
||||
const isSaveToDisk = saveType ? saveType[item.fileId] : false;
|
||||
// 获取下载次数
|
||||
// Get download count
|
||||
const downloadCount = downloadCounts[item.fileId] || 0;
|
||||
|
||||
if (messages === null) {
|
||||
@@ -269,7 +268,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
}
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{progress && progress.progress < 1 ? ( //显示进度或已完成
|
||||
{progress && progress.progress < 1 ? ( //Show progress or completed
|
||||
<TransferProgress
|
||||
message={
|
||||
mode === "sender"
|
||||
@@ -285,7 +284,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
) : null}
|
||||
{mode === "receiver" &&
|
||||
onRequest &&
|
||||
onDownload && ( //请求 && 下载
|
||||
onDownload && ( //Request && Download
|
||||
<FileTransferButton
|
||||
onRequest={() => onRequest(item)}
|
||||
isCurrentFileTransferring={
|
||||
@@ -297,7 +296,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
isSavedToDisk={saveType ? saveType[item.fileId] : false}
|
||||
/>
|
||||
)}
|
||||
{/* 展示下载次数 */}
|
||||
{/* display download Num*/}
|
||||
{mode === "sender" && (
|
||||
<span className="mr-2 text-sm">
|
||||
{messages.text.FileListDisplay.downloadNum_dis}: {downloadCount}
|
||||
@@ -321,7 +320,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
</div>
|
||||
);
|
||||
};
|
||||
//每一项文件 对应的展示--meta信息
|
||||
//Display of each file --meta information
|
||||
const renderItem = (item: FileMeta, isFolder: boolean) => {
|
||||
const filenameDisplayLen = 30;
|
||||
const formatSize = formatFileSize(item.size);
|
||||
@@ -362,7 +361,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
<>
|
||||
{(singleFiles.length > 0 || folders.length > 0) && (
|
||||
<>
|
||||
{/* 自动弹窗组件,当有大文件和文件夹时 只提醒一次 */}
|
||||
{/* Automatic pop-up component, only remind once when there are large files and folders */}
|
||||
{mode === "receiver" && (
|
||||
<div className="mb-2">
|
||||
<AutoPopupDialog
|
||||
@@ -373,7 +372,7 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
}
|
||||
condition={() => needPickLocation}
|
||||
/>
|
||||
{/* 常态化提醒选择保存目录 */}
|
||||
{/* Regular reminder to select the save directory */}
|
||||
<div className="flex items-center">
|
||||
<p className="text-red-500 mb-2">
|
||||
{messages.text.FileListDisplay.chooseSavePath_tips}
|
||||
@@ -395,7 +394,6 @@ const FileListDisplay: React.FC<FileListDisplayProps> = ({
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-2">
|
||||
{/* 确保文件列表确实被渲染 */}
|
||||
<div className="files-list">
|
||||
{singleFiles.map((file) => (
|
||||
<div key={`single-${file.name}`}>{renderItem(file, false)}</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ interface FileTransferButtonProps {
|
||||
isOtherFileTransferring: boolean;
|
||||
isSavedToDisk: boolean;
|
||||
}
|
||||
//针对下载中不同状态的按键进行管理
|
||||
// Manage buttons for different download statuses
|
||||
const FileTransferButton = ({
|
||||
onRequest,
|
||||
isCurrentFileTransferring,
|
||||
@@ -21,7 +21,7 @@ const FileTransferButton = ({
|
||||
}: FileTransferButtonProps) => {
|
||||
const locale = useLocale();
|
||||
const [messages, setMessages] = useState<Messages | null>(null);
|
||||
// 按钮状态判断
|
||||
// Button status judgment
|
||||
const isDisabled = isCurrentFileTransferring || isSavedToDisk || isOtherFileTransferring;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -29,7 +29,7 @@ const FileTransferButton = ({
|
||||
.then(dict => setMessages(dict))
|
||||
.catch(error => console.error('Failed to load messages:', error));
|
||||
}, [locale]);
|
||||
// 根据不同状态显示不同的提示信息
|
||||
// Display different tooltips based on status
|
||||
const getTooltipContent = () => {
|
||||
if (isSavedToDisk) return messages!.text.FileTransferButton.SavedToDisk_tips;
|
||||
if (isCurrentFileTransferring) return messages!.text.FileTransferButton.CurrentFileTransferring_tips;
|
||||
@@ -37,7 +37,7 @@ const FileTransferButton = ({
|
||||
return messages!.text.FileTransferButton.download_tips;
|
||||
};
|
||||
|
||||
// 根据状态设置不同的按钮样式和类名
|
||||
// Set different button styles and class names based on status
|
||||
const getButtonStyles = () => {
|
||||
if (isSavedToDisk) {
|
||||
return {
|
||||
@@ -71,7 +71,7 @@ const FileTransferButton = ({
|
||||
<TooltipProvider delayDuration={100}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="inline-block"> {/* 包装器确保禁用状态下tooltip仍然工作 */}
|
||||
<span className="inline-block">
|
||||
<Button
|
||||
onClick={onRequest}
|
||||
variant={buttonStyles.variant}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
} from '@/components/ui/dialog';
|
||||
// 在文件顶部添加这个声明来扩展已有的类型,避免IDE报错
|
||||
// Add this declaration at the top of the file to extend existing types and avoid IDE errors
|
||||
declare module "@/components/ui/input" {
|
||||
interface InputProps {
|
||||
webkitdirectory?: string | boolean;
|
||||
@@ -20,7 +20,7 @@ declare module "@/components/ui/input" {
|
||||
import { getDictionary } from '@/lib/dictionary';
|
||||
import { useLocale } from '@/hooks/useLocale';
|
||||
import type { Messages } from '@/types/messages';
|
||||
import { en } from '@/constants/messages/en'; // 导入英文字典作为默认值
|
||||
import { en } from '@/constants/messages/en'; // Import English dictionary as default
|
||||
|
||||
const traverseFileTree = async (item: FileSystemEntry, path = ''): Promise<CustomFile[]> => {
|
||||
return new Promise((resolve) => {
|
||||
@@ -49,7 +49,7 @@ const traverseFileTree = async (item: FileSystemEntry, path = ''): Promise<Custo
|
||||
// console.log('subResults',subResults)
|
||||
const files: CustomFile[] = subResults.flat();
|
||||
// console.log('files',files)
|
||||
resolve(files); // 移除了条件判断,直接返回处理好的文件
|
||||
resolve(files); // Removed conditional judgment, directly return processed files
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -76,17 +76,17 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
onFilePicked
|
||||
}) => {
|
||||
const locale = useLocale();
|
||||
const [messages, setMessages] = useState<Messages>(en); // 使用英文字典作为初始值
|
||||
const [messages, setMessages] = useState<Messages>(en); // Use English dictionary as initial value
|
||||
|
||||
const dropZoneRef = useRef<HTMLDivElement>(null);//拖拽文件至附件--支持
|
||||
const dropZoneRef = useRef<HTMLDivElement>(null);// Drag and drop files to attachments -- support
|
||||
const folderInputRef = useRef<HTMLInputElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
// 文件选择器--消息提示
|
||||
// File selector -- message prompt
|
||||
const [fileText, setFileText] = useState<string>(en.text.fileUploadHandler.NoFileChosen_tips);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (locale !== 'en') { // 如果不是英文,才需要加载其他语言包
|
||||
if (locale !== 'en') { // Only load other language packs if not English
|
||||
getDictionary(locale)
|
||||
.then(dict => {setMessages(dict);setFileText(dict.text.fileUploadHandler.NoFileChosen_tips);})
|
||||
.catch(error => console.error('Failed to load messages:', error));
|
||||
@@ -100,7 +100,6 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
const fileNum = newFiles.length;
|
||||
const folderNum = newFiles.filter(file => file.folderName).length;
|
||||
|
||||
// 使用时
|
||||
const choose_dis = formatFileChosen(
|
||||
messages!.text.fileUploadHandler.fileChosen_tips_template, fileNum, folderNum
|
||||
);
|
||||
@@ -112,7 +111,7 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
}, [messages,onFilePicked]);
|
||||
//拖拽上传文件夹 响应处理
|
||||
// Drag and drop folder upload response processing
|
||||
const handleDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -132,15 +131,15 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
});
|
||||
}
|
||||
}, [handleFileChange]);
|
||||
/* 定义一个处理拖动文件悬停事件的回调函数 handleDragOver。
|
||||
在 handleDragOver 中,阻止默认行为和事件传播,以确保自定义处理。
|
||||
没有依赖项数组,这意味着 handleDragOver 函数只会在组件第一次渲染时创建一次,并且不会在之后的渲染中重新创建
|
||||
/* Define a callback function handleDragOver to handle the drag-over event.
|
||||
In handleDragOver, prevent default behavior and event propagation to ensure custom handling.
|
||||
There is no dependency array, which means the handleDragOver function will only be created once when the component first renders, and will not be re-created in subsequent renders.
|
||||
*/
|
||||
const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}, []);
|
||||
//点击上传文件 处理
|
||||
// Click to upload file processing
|
||||
const handleFileInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) {
|
||||
const files = Array.from(e.target.files);
|
||||
@@ -150,10 +149,10 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
files2.push(customFile);
|
||||
}
|
||||
handleFileChange(files2);
|
||||
setIsModalOpen(false);//关闭对话框
|
||||
setIsModalOpen(false);// Close the dialog
|
||||
}
|
||||
}, [handleFileChange]);
|
||||
//点击上传文件夹 响应处理
|
||||
// Click to upload folder response processing
|
||||
const handleFolderInputChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.target.files) {
|
||||
const files_ = Array.from(e.target.files);
|
||||
@@ -167,20 +166,20 @@ const FileUploadHandler: React.FC<FileUploadHandlerProps> = ({
|
||||
});
|
||||
|
||||
handleFileChange(files);
|
||||
setIsModalOpen(false);//关闭对话框
|
||||
setIsModalOpen(false);// Close the dialog
|
||||
}
|
||||
}, [handleFileChange]);
|
||||
|
||||
// 处理拖放区域的点击
|
||||
// Handle drag and drop area click
|
||||
const handleZoneClick = () => {
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
// 处理选择文件
|
||||
// Handle file selection
|
||||
const handleSelectFile = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
// 处理选择文件夹
|
||||
// Handle folder selection
|
||||
const handleSelectFolder = () => {
|
||||
folderInputRef.current?.click();
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import FileListDisplay from "@/components/ClipboardApp/FileListDisplay";
|
||||
import type { Messages } from "@/types/messages";
|
||||
import type { FileMeta } from "@/types/webrtc";
|
||||
import type { ProgressState } from "@/hooks/useWebRTCConnection"; // 假设此类型已导出
|
||||
import type { ProgressState } from "@/hooks/useWebRTCConnection"; // Assuming this type is exported
|
||||
import type WebRTC_Recipient from "@/lib/webrtc_Recipient";
|
||||
|
||||
interface RetrieveTabPanelProps {
|
||||
|
||||
@@ -37,8 +37,8 @@ interface SendTabPanelProps {
|
||||
removeFileToSend: (meta: FileMeta) => void;
|
||||
richTextToPlainText: (html: string) => string;
|
||||
sendProgress: ProgressState;
|
||||
// shareRoomId: string; // 这个是从 useRoomManager 来的,代表已验证的ID
|
||||
processRoomIdInput: (roomId: string) => void; // 从 useRoomManager 传入
|
||||
// shareRoomId: string; // This comes from useRoomManager and represents the validated ID
|
||||
processRoomIdInput: (roomId: string) => void; // Passed from useRoomManager
|
||||
joinRoom: (isSender: boolean, roomId: string) => void;
|
||||
generateShareLinkAndBroadcast: () => void;
|
||||
sender: WebRTC_Initiator | null;
|
||||
@@ -67,12 +67,12 @@ export function SendTabPanel({
|
||||
currentValidatedShareRoomId,
|
||||
}: // initShareRoomId,
|
||||
SendTabPanelProps) {
|
||||
// 本地状态,用于输入框的即时响应
|
||||
// Local state for immediate response in the input field
|
||||
const [inputFieldValue, setInputFieldValue] = useState<string>(
|
||||
currentValidatedShareRoomId
|
||||
);
|
||||
|
||||
// 当来自父组件的 validatedShareRoomId 改变时(例如,初始获取后),同步本地输入框的值
|
||||
// When the validatedShareRoomId from the parent component changes (e.g., after initial fetch), synchronize the local input field's value
|
||||
useEffect(() => {
|
||||
setInputFieldValue(currentValidatedShareRoomId);
|
||||
}, [currentValidatedShareRoomId]);
|
||||
@@ -80,8 +80,8 @@ SendTabPanelProps) {
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = e.target.value;
|
||||
setInputFieldValue(newValue); // 立即更新输入框显示
|
||||
processRoomIdInput(newValue); // 调用hook中的处理函数,它会进行防抖验证
|
||||
setInputFieldValue(newValue); // Immediately update the input field display
|
||||
processRoomIdInput(newValue); // Call the handler function from the hook, which will perform debounced validation
|
||||
},
|
||||
[processRoomIdInput]
|
||||
);
|
||||
@@ -129,18 +129,14 @@ SendTabPanelProps) {
|
||||
</span>
|
||||
<Input
|
||||
aria-label="Share Room ID"
|
||||
value={inputFieldValue} // 绑定到本地状态
|
||||
value={inputFieldValue} // Bind to local state
|
||||
onChange={handleInputChange}
|
||||
onPaste={handlePaste}
|
||||
className="flex-grow min-w-0"
|
||||
// placeholder={
|
||||
// // messages.text.ClipboardApp.html.roomIdInput_placeholder ||
|
||||
// "输入房间号或粘贴"
|
||||
// }
|
||||
/>
|
||||
<Button
|
||||
className="w-full sm:w-auto"
|
||||
onClick={() => joinRoom(true, inputFieldValue.trim())} // 使用当前输入框的值尝试加入
|
||||
onClick={() => joinRoom(true, inputFieldValue.trim())} // Attempt to join using the current input field value
|
||||
disabled={!sender || sender.isInRoom || !inputFieldValue.trim()}
|
||||
>
|
||||
{messages.text.ClipboardApp.html.joinRoom_dis}
|
||||
@@ -156,7 +152,7 @@ SendTabPanelProps) {
|
||||
!sender.isInRoom ||
|
||||
(sendFiles.length === 0 && shareContent.trim() === "") ||
|
||||
!currentValidatedShareRoomId.trim()
|
||||
} // 确保有已验证的房间ID才可分享
|
||||
} // Ensure there is a validated room ID before allowing sharing
|
||||
>
|
||||
{messages.text.ClipboardApp.html.startSending_dis}
|
||||
</AnimatedButton>
|
||||
|
||||
@@ -98,9 +98,9 @@ const ShareCard: React.FC<ShareCardProps> = ({ RoomID, shareLink }) => {
|
||||
<div className="bg-blue-100 p-4 rounded-md">
|
||||
<p className="text-blue-700 mb-4">{messages.text.RetrieveMethod.P}</p>
|
||||
|
||||
{/* 使用 flex-col 替代 list,更好控制移动端布局 */}
|
||||
{/* Use flex-col instead of list for better control on mobile layout */}
|
||||
<div className="flex flex-col space-y-4">
|
||||
{/* RoomID 部分 */}
|
||||
{/* RoomID section */}
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span>{messages.text.RetrieveMethod.RoomId_tips + RoomID}</span>
|
||||
@@ -111,7 +111,7 @@ const ShareCard: React.FC<ShareCardProps> = ({ RoomID, shareLink }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* URL 部分 */}
|
||||
{/* URL section */}
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div className="break-all">
|
||||
{messages.text.RetrieveMethod.url_tips + shareLink}
|
||||
@@ -124,7 +124,7 @@ const ShareCard: React.FC<ShareCardProps> = ({ RoomID, shareLink }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* QR Code 部分 */}
|
||||
{/* QR Code section */}
|
||||
<div className="flex flex-col space-y-2">
|
||||
<div>{messages.text.RetrieveMethod.scanQR_tips}</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
@@ -157,7 +157,7 @@ const ShareCard: React.FC<ShareCardProps> = ({ RoomID, shareLink }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* QR Code 显示区域 */}
|
||||
{/* QR Code display area */}
|
||||
<div className="mt-4 flex justify-center">
|
||||
<div className="inline-block border-2 p-4 bg-white rounded-lg">
|
||||
<div ref={qrRef}>
|
||||
|
||||
@@ -5,7 +5,7 @@ interface TransferProgressProps {
|
||||
message: string;
|
||||
progress: Progress;
|
||||
}
|
||||
//'Sending' : 'Receiving'
|
||||
// Display 'Sending' or 'Receiving' message
|
||||
const TransferProgress: React.FC<TransferProgressProps> = ({ message, progress }) => {
|
||||
const speed = isNaN(progress.speed) ? 0 : progress.speed;
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ interface ArticleListItemProps {
|
||||
export function ArticleListItem({ post }: ArticleListItemProps) {
|
||||
return (
|
||||
<article className="bg-white rounded-xl shadow-lg hover:shadow-xl transition-shadow overflow-hidden">
|
||||
{/* 增大封面图高度 */}
|
||||
<div className="relative h-80 w-full">
|
||||
<Image
|
||||
src={post.frontmatter.cover}
|
||||
@@ -21,7 +20,7 @@ export function ArticleListItem({ post }: ArticleListItemProps) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-8"> {/* 增大内边距 */}
|
||||
<div className="p-8">
|
||||
<div className="flex items-center gap-4 text-sm text-gray-500 mb-4">
|
||||
<time className="font-medium">
|
||||
{new Date(post.frontmatter.date).toLocaleDateString()}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Image from 'next/image'
|
||||
import { ComponentProps, DetailedHTMLProps, HTMLAttributes } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
// 动态导入 Mermaid 组件
|
||||
// Dynamically import the Mermaid component
|
||||
const Mermaid = dynamic(() => import('@/components/blog/Mermaid'), { ssr: false });
|
||||
|
||||
export type MDXComponents = {
|
||||
@@ -126,6 +126,6 @@ export const mdxComponents: MDXComponents = {
|
||||
{children}
|
||||
</li>
|
||||
),
|
||||
mermaid: Mermaid, // 使用定义的 Mermaid 组件
|
||||
mermaid: Mermaid, // Use the defined Mermaid component
|
||||
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client' // 标记为客户端组件
|
||||
'use client' // Mark as client component
|
||||
|
||||
import mermaid from 'mermaid'
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
// 初始化 Mermaid.js
|
||||
// Initialize Mermaid.js
|
||||
mermaid.initialize({ startOnLoad: false })
|
||||
|
||||
const Mermaid: React.FC<{ children: string }> = ({ children }) => {
|
||||
|
||||
@@ -16,30 +16,30 @@ export const TableOfContents: React.FC<TableOfContentsProps> = ({ content }) =>
|
||||
const [activeId, setActiveId] = useState<string>('');
|
||||
const [toc, setToc] = useState<TocItem[]>([]);
|
||||
|
||||
// 生成合法的 ID,保留中文字符
|
||||
// Generate a valid ID, preserving Chinese characters
|
||||
const generateValidId = (text: string): string => {
|
||||
return encodeURIComponent(text
|
||||
.trim() // 移除首尾空格
|
||||
.replace(/\s+/g, '-') // 将空格替换为连字符
|
||||
.replace(/\-\-+/g, '-') // 将多个连字符替换为单个
|
||||
.replace(/^-+/, '') // 移除开头的连字符
|
||||
.replace(/-+$/, '') // 移除结尾的连字符
|
||||
.trim() // Remove leading/trailing spaces
|
||||
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
||||
.replace(/\-\-+/g, '-') // Replace multiple hyphens with a single one
|
||||
.replace(/^-+/, '') // Remove leading hyphens
|
||||
.replace(/-+$/, '') // Remove trailing hyphens
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 解析内容生成目录
|
||||
// Parse content to generate table of contents
|
||||
const headingRegex = /^(#{1,3})\s+(.+)$/gm;
|
||||
const items: TocItem[] = [];
|
||||
let match;
|
||||
const usedIds = new Set<string>(); // 用于跟踪已使用的ID
|
||||
const usedIds = new Set<string>(); // Used to track used IDs
|
||||
|
||||
while ((match = headingRegex.exec(content)) !== null) {
|
||||
const level = match[1].length;
|
||||
const text = match[2].trim();
|
||||
let id = generateValidId(text);
|
||||
|
||||
// 如果ID已存在,添加数字后缀
|
||||
// If ID already exists, add a numeric suffix
|
||||
let counter = 1;
|
||||
let uniqueId = id;
|
||||
while (usedIds.has(uniqueId)) {
|
||||
@@ -66,30 +66,30 @@ export const TableOfContents: React.FC<TableOfContentsProps> = ({ content }) =>
|
||||
{ rootMargin: '-80px 0px -40% 0px' }
|
||||
);
|
||||
|
||||
// 确保所有标题都已经渲染
|
||||
// Ensure all headings are rendered
|
||||
const setupObserver = () => {
|
||||
const headers = document.querySelectorAll('h1[id], h2[id], h3[id]');
|
||||
headers.forEach((header) => observer.observe(header));
|
||||
};
|
||||
|
||||
// 确保 DOM 已更新
|
||||
// Ensure DOM is updated
|
||||
if (toc.length > 0) {
|
||||
// 给 DOM 一点时间来更新
|
||||
// Give the DOM some time to update
|
||||
setTimeout(setupObserver, 100);
|
||||
}
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [toc]); // 依赖于 toc 而不是 content
|
||||
}, [toc]); // Depends on toc instead of content
|
||||
|
||||
const scrollToHeader = (id: string) => {
|
||||
// 不需要解码 ID,因为它已经是正确的格式
|
||||
// No need to decode the ID, as it is already in the correct format
|
||||
const element = document.getElementById(id);
|
||||
if (element) {
|
||||
// 获取元素位置
|
||||
// Get element position
|
||||
const rect = element.getBoundingClientRect();
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||
|
||||
// 计算目标位置(考虑固定导航栏的高度,假设是 80px)
|
||||
// Calculate target position (considering the fixed navigation bar height, assuming 80px)
|
||||
const offsetTop = rect.top + scrollTop - 80;
|
||||
|
||||
window.scrollTo({
|
||||
@@ -97,7 +97,7 @@ export const TableOfContents: React.FC<TableOfContentsProps> = ({ content }) =>
|
||||
behavior: 'smooth'
|
||||
});
|
||||
|
||||
// 设置当前活动项
|
||||
// Set current active item
|
||||
setActiveId(id);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
import Image from 'next/image'
|
||||
import type { Messages } from '@/types/messages';
|
||||
import Image from "next/image";
|
||||
import type { Messages } from "@/types/messages";
|
||||
|
||||
interface PageContentProps {
|
||||
messages: Messages;
|
||||
}
|
||||
|
||||
export default function SystemDiagram({ messages }: PageContentProps) {
|
||||
|
||||
return (
|
||||
<section className="py-16 bg-background">
|
||||
<div className="container mx-auto px-4">
|
||||
<h2 className="text-3xl font-bold mb-12 text-center">{messages.text.SystemDiagram.h2}</h2>
|
||||
<Image src="/SystemDiagram.png" alt="SecureShare system diagram: Peer-to-peer file and clipboard sharing" width={1226} height={745} className="mx-auto mb-6" />
|
||||
<h2 className="text-3xl font-bold mb-12 text-center">
|
||||
{messages.text.SystemDiagram.h2}
|
||||
</h2>
|
||||
<Image
|
||||
src="/SystemDiagram.png"
|
||||
alt="SecureShare system diagram: Peer-to-peer file and clipboard sharing"
|
||||
width={1226}
|
||||
height={745}
|
||||
className="mx-auto mb-6"
|
||||
/>
|
||||
<p className="mt-8 text-center max-w-2xl mx-auto">
|
||||
{messages.text.SystemDiagram.h2_P}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user