移动端逻辑:终端滚动与上传按钮
This commit is contained in:
1 parent
6f5123c307
commit
be92ddd55d
2 files changed
+85
-15
No files matched your search
@@ -148,6 +148,7 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo
|
||||
const [dialogInput, setDialogInput] = React.useState("");
|
||||
const [chmodRecursive, setChmodRecursive] = React.useState(false);
|
||||
const [chmodLoading, setChmodLoading] = React.useState(false);
|
||||
const fileInputRef = React.useRef<HTMLInputElement | null>(null);
|
||||
const directoryCacheRef = React.useRef<Map<string, DirectoryCacheItem>>(new Map());
|
||||
const treeRef = React.useRef<TreeNode[]>(tree);
|
||||
const selectedPathRef = React.useRef<string>(selectedPath);
|
||||
@@ -517,11 +518,11 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo
|
||||
[mapBatchFailures, uploadBatchWithRetry, t]
|
||||
);
|
||||
|
||||
// 处理文件拖拽上传
|
||||
const handleDrop = React.useCallback(async (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(false);
|
||||
|
||||
const handleUploadFiles = React.useCallback(async (allFiles: Array<{ file: File; path: string }>) => {
|
||||
if (allFiles.length === 0) {
|
||||
push(t("没有可上传的文件", "No files to upload"));
|
||||
return;
|
||||
}
|
||||
setUploading(true);
|
||||
setUploadProgress([]);
|
||||
setLastUploadFailures([]);
|
||||
@@ -530,15 +531,6 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo
|
||||
setUploadStats(INITIAL_UPLOAD_STATS);
|
||||
|
||||
try {
|
||||
// 读取所有文件(包括文件夹内容)
|
||||
const dropSnapshot = snapshotDropPayload(e.dataTransfer);
|
||||
const allFiles = await collectDroppedFiles(dropSnapshot);
|
||||
|
||||
if (allFiles.length === 0) {
|
||||
push(t("没有可上传的文件", "No files to upload"));
|
||||
return;
|
||||
}
|
||||
|
||||
const totalBytes = calculateTotalBytes(allFiles);
|
||||
setUploadStats({ current: 0, total: allFiles.length, uploadedBytes: 0, totalBytes });
|
||||
|
||||
@@ -639,6 +631,34 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo
|
||||
}
|
||||
}, [sessionId, selectedPath, compress, push, loadDirectory, uploadFilesByBatch, t, language]);
|
||||
|
||||
// 处理文件拖拽上传
|
||||
const handleDrop = React.useCallback(async (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(false);
|
||||
|
||||
try {
|
||||
// 读取所有文件(包括文件夹内容)
|
||||
const dropSnapshot = snapshotDropPayload(e.dataTransfer);
|
||||
const allFiles = await collectDroppedFiles(dropSnapshot);
|
||||
await handleUploadFiles(allFiles);
|
||||
} catch (err) {
|
||||
push(err instanceof Error ? err.message : t("上传失败", "Upload failed"));
|
||||
}
|
||||
}, [handleUploadFiles, push, t]);
|
||||
|
||||
const handleFileInputChange = React.useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const filesList = event.target.files;
|
||||
if (!filesList || filesList.length === 0) {
|
||||
return;
|
||||
}
|
||||
const allFiles = Array.from(filesList).map((file) => ({
|
||||
file,
|
||||
path: file.webkitRelativePath || file.name,
|
||||
}));
|
||||
await handleUploadFiles(allFiles);
|
||||
event.target.value = "";
|
||||
}, [handleUploadFiles]);
|
||||
|
||||
const handleCancelUpload = React.useCallback(() => {
|
||||
uploadCancelledRef.current = true;
|
||||
}, []);
|
||||
@@ -1158,6 +1178,14 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo
|
||||
>
|
||||
{t("新建文件夹", "New folder")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={uploading}
|
||||
className={`rounded-full px-3 py-2 ${uploading ? "opacity-50 cursor-not-allowed" : ""} ${isDark ? "bg-slate-800 text-slate-200" : "bg-white text-slate-700 border border-slate-200"}`}
|
||||
>
|
||||
{t("上传文件", "Upload")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleActionForFiles("touch", [])}
|
||||
@@ -1205,6 +1233,13 @@ export function FileBrowser({ sessionId, isDark, currentDir, onFileSelect, netwo
|
||||
>
|
||||
{t("取消选择", "Clear")}
|
||||
</button>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
multiple
|
||||
className="hidden"
|
||||
onChange={handleFileInputChange}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -27,6 +27,15 @@ export function TerminalMobile({ state, onBack }: TerminalMobileProps) {
|
||||
term.focus();
|
||||
}, [activeTab, syncTerminalSize, terminalInstance]);
|
||||
|
||||
const handleScroll = React.useCallback((direction: "up" | "down") => {
|
||||
const term = terminalInstance.current;
|
||||
if (!term) {
|
||||
return;
|
||||
}
|
||||
const lines = direction === "up" ? -8 : 8;
|
||||
term.scrollLines(lines);
|
||||
}, [terminalInstance]);
|
||||
|
||||
const actions = [
|
||||
{
|
||||
key: "language",
|
||||
@@ -132,13 +141,39 @@ export function TerminalMobile({ state, onBack }: TerminalMobileProps) {
|
||||
|
||||
<div className="flex-1 min-h-0 pb-16">
|
||||
<div className={`${activeTab === "terminal" ? "flex" : "hidden"} h-full flex-col`}>
|
||||
<div className="flex-1 p-3 min-h-0">
|
||||
<div className="flex-1 p-3 min-h-0 relative">
|
||||
<div
|
||||
ref={state.terminalRef}
|
||||
className={`h-full w-full rounded-lg border ${state.isDark ? "border-slate-800" : "border-slate-300 xterm-light"} ${state.sessionInfo?.enhanced_enabled ? "xterm-tmux-enhanced" : ""}`}
|
||||
tabIndex={-1}
|
||||
onClick={() => state.terminalInstance.current?.focus()}
|
||||
/>
|
||||
<div className="absolute right-4 bottom-4 flex flex-col gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleScroll("up")}
|
||||
className={`h-10 w-10 rounded-full border text-base ${
|
||||
state.isDark
|
||||
? "border-slate-700 bg-slate-900/90 text-slate-200"
|
||||
: "border-slate-200 bg-white text-slate-700"
|
||||
}`}
|
||||
aria-label={state.t("向上滚动终端", "Scroll terminal up")}
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleScroll("down")}
|
||||
className={`h-10 w-10 rounded-full border text-base ${
|
||||
state.isDark
|
||||
? "border-slate-700 bg-slate-900/90 text-slate-200"
|
||||
: "border-slate-200 bg-white text-slate-700"
|
||||
}`}
|
||||
aria-label={state.t("向下滚动终端", "Scroll terminal down")}
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in new issue
Block a user