From b01901a32a2e6eb1c64e9da94ee754330d944590 Mon Sep 17 00:00:00 2001 From: Yuting Nie Date: Mon, 28 Aug 2023 23:33:20 +0800 Subject: [PATCH] The enhanced frontend functionalities: navigate back to previous level; breadcrumb links; homepage to navigate; display file information. Signed-off-by: Yuting Nie --- ui/package-lock.json | 14 +- ui/package.json | 2 +- ui/src/app/api/api.json | 0 ui/src/pages/codeViewComponent.js | 237 +++++++++++++++------ ui/src/pages/codeViewComponent.modules.css | 12 +- ui/src/pages/index.css | 21 +- ui/src/pages/index.js | 16 +- 7 files changed, 222 insertions(+), 80 deletions(-) create mode 100644 ui/src/app/api/api.json diff --git a/ui/package-lock.json b/ui/package-lock.json index 7222cc33..070d39a6 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -22588,9 +22588,9 @@ } }, "node_modules/webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmmirror.com/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", + "version": "5.88.1", + "resolved": "https://registry.npmmirror.com/webpack/-/webpack-5.88.1.tgz", + "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", @@ -41298,9 +41298,9 @@ "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" }, "webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmmirror.com/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", + "version": "5.88.1", + "resolved": "https://registry.npmmirror.com/webpack/-/webpack-5.88.1.tgz", + "integrity": "sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==", "requires": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", @@ -41990,4 +41990,4 @@ "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==" } } -} +} \ No newline at end of file diff --git a/ui/package.json b/ui/package.json index 29fee855..5594b5ff 100644 --- a/ui/package.json +++ b/ui/package.json @@ -66,4 +66,4 @@ "postcss": "^7.0.39", "tailwindcss": "^3.3.2" } -} +} \ No newline at end of file diff --git a/ui/src/app/api/api.json b/ui/src/app/api/api.json new file mode 100644 index 00000000..e69de29b diff --git a/ui/src/pages/codeViewComponent.js b/ui/src/pages/codeViewComponent.js index a0f641a8..980de139 100644 --- a/ui/src/pages/codeViewComponent.js +++ b/ui/src/pages/codeViewComponent.js @@ -38,6 +38,7 @@ import { UnControlled as CodeMirror } from 'react-codemirror2'; import Breadcrumbs from '@mui/material/Breadcrumbs'; import Link from '@mui/material/Link'; import axios from 'axios'; +import { Typography } from '@mui/material'; import { useSearchParams } from 'next/navigation'; const timeline = [ @@ -127,11 +128,13 @@ const getFileLanguageMode = (fileName) => { }; export default function Code_view() { + const router = useRouter(); + const { itemId, itemType } = router.query; + const [is_node_clicked, setis_node_clicked] = useState(true); const [is_file_clicked, setis_file_clicked] = useState(false); - const [showButtons, setShowButtons] = useState(false); + const [show_buttons, setshow_buttons] = useState(false); useEffect(() => { - //使用window判断是否为在浏览器端才执行的代码 if (typeof window !== "undefined" && is_file_clicked) { var Review_Canel_Button = document.getElementsByClassName("review_cancel_button"); @@ -143,7 +146,7 @@ export default function Code_view() { Review_Canel_Button[0].addEventListener('click', function () { lexical_input[0].style.display = "none"; }) - setShowButtons(true); + setshow_buttons(true); const handleButtonClick = (e) => { const lexicalInput = document.getElementsByClassName("review-Editor")[0]; lexicalInput.style.display = "flex"; @@ -195,6 +198,7 @@ export default function Code_view() { if (window.pageYOffset > 200) { return_to_top.style.display = "block"; } else { + setIs return_to_top.style.display = "none"; } } @@ -217,6 +221,9 @@ export default function Code_view() { const [is_first_load, setis_first_load] = useState(true); const [click_history_content, setclick_history_content] = useState([]); const [current_sub_nodes, setcurrent_sub_nodes] = useState(null); + const [clicked_nodeId, setclicked_nodeId] = useState(null); + const [tree_view_key, settree_view_key] = useState(0); + const [is_current_folder, setis_current_folder] = useState(false); // console.log(click_history); const get_icon_for_content_type = (content_type) => { if (content_type === 'directory') { @@ -239,14 +246,23 @@ export default function Code_view() { useEffect(() => { async function get_file_dir() { try { - const response = await axios.get('/api/v1/tree?repo_path=/root/mega.git'); - setdir_file_data(response.data); - setcurrent_sub_nodes(response.data); + const root_response = await axios.get(`/api/v1/tree?repo_path=/root/mega`); + setdir_file_data(root_response.data); + const index_clicked_node = root_response.data.items.find(item => item.id === itemId); + let response; + if (itemType === 'file') { + console.log(itemType); + handle_file_click(index_clicked_node.id, index_clicked_node.name); + } else { + response = await axios.get(`/api/v1/tree?repo_path=/root/mega&object_id=${itemId}`); + setcurrent_sub_nodes(response.data); + } + console.log(index_clicked_node.name); const readmeFile = response.data.items.find(item => item.name === 'README.md'); if (readmeFile && readmeFile.content_type === 'file') { async function getReadmeContent() { try { - const response = await axios.get(`/api/v1/blob?repo_path=/root/mega.git&object_id=${readmeFile.id}`); + const response = await axios.get(`/api/v1/blob?repo_path=/root/mega&object_id=${readmeFile.id}`); setReadmeContent(response.data.row_data); } catch (error) { console.error(error); @@ -258,6 +274,11 @@ export default function Code_view() { } if (is_first_load) { setsub_file_dir(response.data); + setclick_history_content([{ id: "root", name: "MEGA" }]); + setclick_history_content((prevHistory) => [ + ...prevHistory, + { id: index_clicked_node.id, name: index_clicked_node.name, isFile: false }, // 添加 isFile 属性以区分文件和文件夹 + ]); } } catch (error) { console.error(error); @@ -267,21 +288,15 @@ export default function Code_view() { }, []); //获取项目的子目录, 且扫描子目录下是否有readme文件 - const get_sub_directory_data = async (directory_name, directoryId) => { + const get_sub_directory_data = async (directory_name, directoryId, item) => { try { - const response = await axios.get(`/api/v1/tree?repo_path=/root/mega.git&object_id=${directoryId}`); + const response = await axios.get(`/api/v1/tree?repo_path=/root/mega&object_id=${directoryId}`); setcurrent_sub_nodes(response.data); const readmeFile = response.data.items.find(item => item.name === 'README.md'); - setclick_history_content([ - ...click_history_content, - { name: directory_name, id: directoryId } - ]); - console.log(click_history_content); - handle_history_click(directory_name, directoryId); if (readmeFile && readmeFile.content_type === 'file') { async function getReadmeContent() { try { - const response = await axios.get(`/api/v1/blob?repo_path=/root/mega.git&object_id=${readmeFile.id}`); + const response = await axios.get(`/api/v1/blob?repo_path=/root/mega&object_id=${readmeFile.id}`); setReadmeContent(response.data.row_data); } catch (error) { console.error(error); @@ -302,46 +317,129 @@ export default function Code_view() { // dir点击事件,处理节点展开和关闭 - const handle_history_click = (item_name, item_id) => { - - }; + const [is_table_node_clicked, setis_table_node_clicked] = useState(false); + const [expandedNodes, setExpandedNodes] = useState({}); const handle_node_toggle = async (node, nodeId) => { setis_first_load(false); setis_file_clicked(false); setis_node_clicked(true); + const newExpandedNodes = { ...expandedNodes }; + let parentNode = node.parent; + while (parentNode) { + newExpandedNodes[parentNode.id] = true; + parentNode = parentNode.parent; + } + // 如果节点是目录,则进行加载 if (node && node.content_type === 'directory') { - const newChildren = await get_sub_directory_data(node.name, node.id); - if (newChildren) { - node.children = newChildren.items; - setsub_file_dir(newChildren); - setdir_file_data({ ...dir_file_data }); // 更新状态 - + if (expandedNodes[nodeId]) { + setExpandedNodes(prevExpandedNodes => ({ + ...prevExpandedNodes, + [nodeId]: false + })); + } else { + // 展开节点及其所有父节点 + const newExpandedNodes = { ...expandedNodes }; + const expandNodeAndParents = (currentNode) => { + newExpandedNodes[currentNode.id] = true; + if (currentNode.parentId) { + const parent = dir_file_data.items.find(item => item.id === currentNode.parentId); + if (parent) { + expandNodeAndParents(parent); + } + } + }; + expandNodeAndParents(node); + setExpandedNodes(newExpandedNodes); + const newChildren = await get_sub_directory_data(node.name, node.id, node); + if (newChildren) { + node.children = newChildren.items; + setsub_file_dir(newChildren); + setdir_file_data({ ...dir_file_data }); // 更新状态 + } } } }; const handle_sub_table_click = async (item) => { + console.log("test items type"); + console.log(item); + setclicked_nodeId(item.id); + settree_view_key((prevKey) => prevKey + 1); if (item.content_type === "file") { handle_file_click(item.id, item.name); + setclick_history_content((prevHistory) => [ + ...prevHistory, + { id: item.id, name: item.name, isFile: true }, // 添加 isFile 属性以区分文件和文件夹 + ]); } else { - const newChildren = await get_sub_directory_data(item.name, item.id); + handle_node_toggle(item, item.id); + const newChildren = await get_sub_directory_data(item.name, item.id, item); setsub_file_dir(newChildren); + setis_table_node_clicked(true); //当列表的文件夹被点击时,设置状态为true, 使得tree组件重新渲染,模拟节点点击逻辑 + setclick_history_content((prevHistory) => [ + ...prevHistory, + { id: item.id, name: item.name, isFile: false }, + ]); + var clickA = new Event("clickA"); + document.dispatchEvent(clickA); + var tree_items = document.getElementsByClassName("MuiTreeItem-root"); + // console.log(click_history_content); + } }; + const handle_level_return = async () => { + // console.log("进入 handle_level_return"); + if (click_history_content.length <= 1) { + // 已经在根目录,不需要返回 + return; + } + const previous_item = click_history_content[click_history_content.length - 2]; + // console.log(previous_item); + setis_current_folder(false); + if (previous_item.id === "root") { + const response = await axios.get('/api/v1/tree?repo_path=/root/mega'); + setsub_file_dir(response.data); + } else { + const newChildren = await get_sub_directory_data(previous_item.name, previous_item.id, previous_item); + setsub_file_dir(newChildren); + // console.log(newChildren); + } + // 从历史中移除当前项 + setclick_history_content(current => current.slice(0, -1)); + } + + const handle_breadcrumb_click = async (index) => { + setis_file_clicked(false); + setis_node_clicked(true); + const clicked_history = click_history_content.slice(0, index + 1); + setclick_history_content(clicked_history); + + if (index === 0) { + // 处理根目录点击 + const response = await axios.get('/api/v1/tree?repo_path=/root/mega'); + setsub_file_dir(response.data); + } else { + const parent_item = clicked_history[index]; + console.log(parent_item); + const newChildren = await get_sub_directory_data(parent_item.name, parent_item.id, parent_item); + setsub_file_dir(newChildren); + } + }; //处理file点击事件,获得文本内容 - const [fileContent, setFileContent] = useState(null); - const [fileMode, setFileMode] = useState('javascript'); + const [file_content, setfile_content] = useState(null); + const [fileMode, setfile_mode] = useState('javascript'); const handle_file_click = async (fileId, fileName) => { try { setis_file_clicked(true); setis_node_clicked(false); - const response = await axios.get(`/api/v1/blob?repo_path=/root/mega.git&object_id=${fileId}`); - setFileContent(response.data.row_data); + setis_table_node_clicked(false); + const response = await axios.get(`/api/v1/blob?repo_path=/root/mega&object_id=${fileId}`); + setfile_content(response.data.row_data); // 获取文件扩展名 const languageMode = getFileLanguageMode(fileName); if (languageMode) { @@ -356,11 +454,10 @@ export default function Code_view() { // 重写 renderTree 组件 - const renderTree = (nodes) => ( + const renderTree = (nodes, clicked_nodeId) => ( <> { nodes.map((node) => { - console.log(node.children); is_dir_expand = false; const { id, name, content_type } = node; if (node.content_type === "file") { @@ -368,26 +465,42 @@ export default function Code_view() { } else { is_dir_expand = true; } - const isFile = content_type === 'file'; + const is_file = content_type === 'file'; + const isExpanded = expandedNodes[node.id]; + const is_clicked = clicked_nodeId === id; const handleNodeClick = () => { - if (isFile) { + if (is_file) { // 如果是文件,处理文件点击事件 handle_file_click(id, name); } else { // 如果是目录,处理目录点击事件 handle_node_toggle(node, id); + setclicked_nodeId(id); } }; if (is_dir_expand) { return ( - - {Array.isArray(node.children) ? renderTree(node.children) : null} + handleNodeClick()} + // style={nodeStyle} // Apply the style based on expanded state + > + {is_clicked && Array.isArray(node.children) && renderTree(node.children, clicked_nodeId)} ); } else { return ( - + {Array.isArray(node.children) ? renderTree(node.children) : null} ); @@ -395,7 +508,6 @@ export default function Code_view() { })} ); - const router = useRouter(); return ( <> @@ -501,12 +613,12 @@ export default function Code_view() {
{dir_file_data && ( } + key={tree_view_key} // 使用状态来触发重新渲染 defaultCollapseIcon={} defaultExpandIcon={} onNodeToggle={handle_node_toggle} > - {renderTree(dir_file_data.items)} + {renderTree(dir_file_data.items, clicked_nodeId)} )}
@@ -516,22 +628,23 @@ export default function Code_view() {
- - MUI - - - Core - - - Breadcrumbs - + {click_history_content.map((historyItem, index) => ( + + {historyItem.isFile ? ( + + {historyItem.name} + + ) : ( + handle_breadcrumb_click(index)} + > + {historyItem.name} + + )} + + ))}
@@ -558,7 +671,7 @@ export default function Code_view() { { is_file_clicked && ( { - setShowButtons(true); + setshow_buttons(true); const handleButtonClick = (e) => { const lexicalInput = document.getElementsByClassName("review-Editor")[0]; lexicalInput.style.display = "flex"; }; - if (showButtons && is_file_clicked) { + if (show_buttons && is_file_clicked) { for (let i = 0; i < editor.lineCount(); i++) { const button = document.createElement("button"); button.textContent = "+"; @@ -619,6 +732,11 @@ export default function Code_view() { )} + + + {sub_file_dir && sub_file_dir.items.map((item) => ( @@ -635,7 +753,6 @@ export default function Code_view() { - )} diff --git a/ui/src/pages/codeViewComponent.modules.css b/ui/src/pages/codeViewComponent.modules.css index 9031bdec..50bf6203 100644 --- a/ui/src/pages/codeViewComponent.modules.css +++ b/ui/src/pages/codeViewComponent.modules.css @@ -92,11 +92,8 @@ height: fit-content; } -.breadcrumb_text { - color: rgb(4, 56, 105); - font-weight: 500; - font-size: small; - +.MuiBreadcrumbs-li { + color: rgb(28, 71, 214); } .Breadcrumb-div { @@ -197,6 +194,11 @@ z-index: 1; } +.return_level_button { + height: 56px; + margin-left: 25px; +} + .editor-container { margin: 20px auto 20px auto; max-width: 1000px; diff --git a/ui/src/pages/index.css b/ui/src/pages/index.css index bf7a6c5c..617d2c40 100644 --- a/ui/src/pages/index.css +++ b/ui/src/pages/index.css @@ -1240,4 +1240,23 @@ video { .file_dir_link:hover { color: rgb(93, 93, 237); -} \ No newline at end of file +} + +.table-content { + table-layout: fixed; + width: 100%; +} + +.text_over_left { + vertical-align: right; + text-align: left; + padding-right: 20px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + -moz-text-overflow: ellipsis; +} + +.text_over_left { + text-align: right; +} diff --git a/ui/src/pages/index.js b/ui/src/pages/index.js index 0eccdbb0..d8f35a6b 100644 --- a/ui/src/pages/index.js +++ b/ui/src/pages/index.js @@ -58,7 +58,7 @@ export default function HomePage() { useEffect(() => { async function get_file_dir() { try { - const response = await axios.get('/api/v1/tree?repo_path=/root/mega.git'); + const response = await axios.get('/api/v1/tree?repo_path=/root/mega'); setdir_file_data(response.data); console.log(dir_file_data); // 检查是否有 README.md 文件 @@ -66,7 +66,7 @@ export default function HomePage() { if (readmeFile && readmeFile.content_type === 'file') { async function getReadmeContent() { try { - const response = await axios.get(`/api/v1/blob?repo_path=/root/mega.git&object_id=${readmeFile.id}`); + const response = await axios.get(`/api/v1/blob?repo_path=/root/mega&object_id=${readmeFile.id}`); setReadmeContent(response.data.row_data); } catch (error) { console.error(error); @@ -83,6 +83,13 @@ export default function HomePage() { get_file_dir(); }, []); const router = useRouter(); + + const handleItemClick = (item) => { + router.push({ + pathname: '/codeViewComponent', + query: { itemId: item.id, itemType: item.content_type } + }); + }; const get_icon_for_content_type = (content_type) => { if (content_type === 'directory') { return ( @@ -232,10 +239,7 @@ export default function HomePage() { {get_icon_for_content_type(item.content_type)}   - router.push({ - pathname: "/codeViewComponent", - - })}>{item.name} + handleItemClick(item)}>{item.name}