Skip to content

Commit

Permalink
feat(portal-web): 优化文件编辑功能 (#1012)
Browse files Browse the repository at this point in the history
# 优化文件编辑功能
## 修改 monaco-editor 默认换行符
monaco 编辑器会自动判断文件的换行符类型。
1. 如果是新建的空文件,则会根据当前运行环境(例如 windows)来决定换行符。
2. 如果不是空文件会根据当前文件的换行符自动判断后续的换行符,如果是 \r\n 后续也是 \r\n 如果原本就是 \n 那后续也是 \n

提交文件运行的模式下,如果文件换行符为 \r\n 时无法提交成功。
用户在 scow 上编写的时 monaco-editor 可能处于 windows 环境,如果在编辑一个新文件就会导致换行符为 \r\n
所以直接将 monaco-editor 的默认换行符设置为 \n,来避免上述情况的发生。但这样对于原本就是 \r\n
的文件,后续编辑会存在两种换行符。

如果要完全避免文件提交运行报错这个问题,还是应该在提交该文件时将换行符进行切换,这种情况仍然会导致该问题:
1. 用户直接上传的文件的换行符为 \r\n
2. 原本的文件就是 \r\n

## 增加流式文件下载中断逻辑
如果流式下载文件未结束时直接关闭模态框打开其他文件模态框会有内容覆盖问题,所需要增加中断流式加载

## 新增文件加载时禁止点击编辑按钮
  • Loading branch information
Miracle575 authored Dec 24, 2023
1 parent d44b477 commit 43c52ee
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 53 deletions.
5 changes: 5 additions & 0 deletions .changeset/empty-dancers-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@scow/portal-web": patch
---

优化文件编辑功能
1 change: 1 addition & 0 deletions apps/portal-web/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ export default {
saveFileFail: "File save failed: {}",
saveFileSuccess: "File saved successfully",
fileSizeExceeded: "File too large (maximum {}), please download and edit",
fileFetchAbortPrompt: "Fetch {} operation was aborted",
},
createFileModal: {
createErrorMessage: "File or directory with the same name already exists!",
Expand Down
1 change: 1 addition & 0 deletions apps/portal-web/src/i18n/zh_cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ export default {
saveFileFail: "文件保存失败: {}",
saveFileSuccess: "文件保存成功",
fileSizeExceeded: "文件过大(最大{}),请下载后编辑",
fileFetchAbortPrompt: "获取文件 {} 操作被终止",
},
createFileModal: {
createErrorMessage: "同名文件或者目录已经存在!",
Expand Down
131 changes: 78 additions & 53 deletions apps/portal-web/src/pageComponents/filemanager/FileEditModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@
import { CloseOutlined, FullscreenExitOutlined, FullscreenOutlined } from "@ant-design/icons";
import Editor, { loader } from "@monaco-editor/react";
import { App, Badge, Button, Modal, Space, Spin, Tabs, Tooltip } from "antd";
import { editor } from "monaco-editor";
import { join } from "path";
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { api } from "src/apis";
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
import { prefix, useI18nTranslateToString } from "src/i18n";
import { publicConfig } from "src/utils/config";
import { convertToBytes } from "src/utils/format";
import { getLanguage } from "src/utils/staticFiles";
import { styled } from "styled-components";

import { urlToUpload } from "./api";
import { urlToDownload, urlToUpload } from "./api";

const StyledTabs = styled(Tabs)`
.ant-tabs-nav {
Expand Down Expand Up @@ -150,14 +150,16 @@ export const FileEditModal: React.FC<Props> = ({ previewFile, setPreviewFile })
const { open, filename, fileSize, filePath, clusterId } = previewFile;

const [mode, setMode] = useState<Mode>(Mode.PREVIEW);
const [fileContent, setFileContent] = useState("");
const [fileContent, setFileContent] = useState<string | undefined>(undefined);
const [isEdit, setIsEdit] = useState(false);
const [loading, setLoading] = useState(false);
const [downloading, setDownloading] = useState(false);
const [saving, setSaving] = useState(false);
const [confirm, setConfirm] = useState(false);
const [exitType, setExitType] = useState<ExitType>(ExitType.CLOSE);
const [isFullScreen, setIsFullScreen] = useState(false);
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
const [abortController, setAbortController] = useState(new AbortController());

const [options, setOptions] = useState({
readOnly: true,
Expand All @@ -170,20 +172,29 @@ export const FileEditModal: React.FC<Props> = ({ previewFile, setPreviewFile })
}
}, [open]);

useEffect(() => {
if (editorRef.current) {
const editorInstance = editorRef.current.getModel();

// 设置换行符为 LF (\n)
editorInstance?.setEOL(0); // 0 代表 LF,1 代表 CRLF
}
}, [editorRef.current]);

const { message } = App.useApp();

const handleEdit = (content) => {
if (content && !downloading) {
if (mode === Mode.EDIT) {
setIsEdit(true);
setFileContent(content);
}
setFileContent(content);
};

const closeProcess = () => {
setConfirm(false);
setIsEdit(false);
setMode(Mode.PREVIEW);
setFileContent("");
setFileContent(undefined);
setOptions({
...options,
readOnly: true,
Expand All @@ -196,6 +207,9 @@ export const FileEditModal: React.FC<Props> = ({ previewFile, setPreviewFile })
};

const handleClose = () => {
if (downloading) {
abortController.abort();
}
if (!isEdit) {
closeProcess();
return;
Expand Down Expand Up @@ -227,6 +241,9 @@ export const FileEditModal: React.FC<Props> = ({ previewFile, setPreviewFile })
};

const handleSave = async () => {
if (fileContent === undefined) {
return;
}

setSaving(true);
const blob = new Blob([fileContent], { type: "text/plain" });
Expand Down Expand Up @@ -258,53 +275,60 @@ export const FileEditModal: React.FC<Props> = ({ previewFile, setPreviewFile })

setLoading(true);
setDownloading(true);
api.downloadFile({
query: { download: false, path: filePath, cluster: clusterId },
}).then((json) => {
setFileContent(JSON.stringify(json, null, 2));
}).catch(async (res: Response) => {

if (!res.ok) {
message.error(t(p("failedGetFile"), [filename]));
return;
}

const reader = res.body?.getReader();
if (reader) {
let accumulatedChunks = "";
let accumulatedSize = 0;
const CHUNK_SIZE = 3 * 1024 * 1024;
const decoder = new TextDecoder();

while (true) {
const { done, value } = await reader.read();
if (done) {
setFileContent(() => {
return accumulatedChunks;
});
break;
}
const chunk = decoder.decode(value, { stream: true });
accumulatedChunks += chunk;
accumulatedSize += chunk.length;

if (accumulatedSize > CHUNK_SIZE) {
setFileContent(() => {
return accumulatedChunks;
});
accumulatedSize = 0;
setLoading(false);
const newAbortController = new AbortController();
setAbortController(newAbortController);
fetch(urlToDownload(clusterId, filePath, false), { signal: newAbortController.signal })
.then((res) => {
if (!res.ok) {
message.error(t(p("failedGetFile"), [filename]));
return;
}
return res.body?.getReader();
})
.then(async (reader) => {
if (reader) {
let accumulatedChunks = "";
let accumulatedSize = 0;
const CHUNK_SIZE = 3 * 1024 * 1024;
const decoder = new TextDecoder();

while (true) {
const { done, value } = await reader.read();
if (done) {
setFileContent(() => {
return accumulatedChunks;
});
break;
}

const chunk = decoder.decode(value, { stream: true });
accumulatedChunks += chunk;
accumulatedSize += chunk.length;

if (accumulatedSize > CHUNK_SIZE) {
setFileContent(() => {
return accumulatedChunks;
});
accumulatedSize = 0;
setLoading(false);
}
}
} else {
message.error(t(p("cantReadFile"), [filename]));
}
} else {
message.error(t(p("cantReadFile"), [filename]));
}

}).finally(() => {
setLoading(false);
setDownloading(false);
});

})
.catch((error) => {
if (error.name === "AbortError") {
message.info(t(p("fileFetchAbortPrompt"), [filename]));
} else {
message.error(t(p("cantReadFile"), [filename]));
}
})
.finally(() => {
setLoading(false);
setDownloading(false);
});
};

const modalTitle = (
Expand Down Expand Up @@ -332,7 +356,7 @@ export const FileEditModal: React.FC<Props> = ({ previewFile, setPreviewFile })
const fileEditLimitSize = publicConfig.FILE_EDIT_SIZE || DEFAULT_FILE_EDIT_LIMIT_SIZE;
return (
mode === Mode.PREVIEW ? (
fileSize <= convertToBytes(fileEditLimitSize)
fileSize <= convertToBytes(fileEditLimitSize) && !downloading
? (
<Button
type="primary"
Expand Down Expand Up @@ -393,7 +417,8 @@ export const FileEditModal: React.FC<Props> = ({ previewFile, setPreviewFile })
defaultLanguage={getLanguage(filename)}
options={options}
value={fileContent}
onChange={handleEdit}
onMount={(editor) => { editorRef.current = editor; }}
onChange={(content) => { !downloading && handleEdit(content); }}
/>
</Spin>
),
Expand Down

0 comments on commit 43c52ee

Please sign in to comment.