import { useState, useEffect, useCallback, Key, useRef } from 'react'; import { TreeSelect, Tree, Input, Button, Modal, message, Typography, Tooltip, Dropdown, Menu, Empty, } from 'antd'; import config from '@/utils/config'; import { PageContainer } from '@ant-design/pro-layout'; import Editor from '@monaco-editor/react'; import { request } from '@/utils/http'; import styles from './index.module.less'; import EditModal from './editModal'; import { Controlled as CodeMirror } from 'react-codemirror2'; import SplitPane from 'react-split-pane'; import { DeleteOutlined, DownloadOutlined, EditOutlined, EllipsisOutlined, FormOutlined, PlusOutlined, PlusSquareOutlined, SearchOutlined, UserOutlined, } from '@ant-design/icons'; import EditScriptNameModal from './editNameModal'; import debounce from 'lodash/debounce'; import { history, useOutletContext, useLocation } from '@umijs/max'; import { parse } from 'query-string'; import { depthFirstSearch } from '@/utils'; import { SharedContext } from '@/layouts'; import useFilterTreeData from '@/hooks/useFilterTreeData'; import { uniq } from 'lodash'; const { Text } = Typography; const LangMap: any = { '.py': 'python', '.js': 'javascript', '.sh': 'shell', '.ts': 'typescript', }; const Script = () => { const { headerStyle, isPhone, theme, socketMessage } = useOutletContext(); const [value, setValue] = useState('请选择脚本文件'); const [select, setSelect] = useState(''); const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const [mode, setMode] = useState(''); const [height, setHeight] = useState(); const treeDom = useRef(); const [isLogModalVisible, setIsLogModalVisible] = useState(false); const [searchValue, setSearchValue] = useState(''); const [isEditing, setIsEditing] = useState(false); const editorRef = useRef(null); const [isAddFileModalVisible, setIsAddFileModalVisible] = useState(false); const [currentNode, setCurrentNode] = useState(); const [expandedKeys, setExpandedKeys] = useState([]); const getScripts = () => { setLoading(true); request .get(`${config.apiPrefix}scripts`) .then(({ code, data }) => { if (code === 200) { setData(data); initGetScript(); } }) .finally(() => setLoading(false)); }; const getDetail = (node: any) => { request .get(`${config.apiPrefix}scripts/${node.title}?path=${node.parent || ''}`) .then(({ code, data }) => { if (code === 200) { setValue(data); } }); }; const initGetScript = () => { const { p, s } = parse(history.location.search); if (s) { const vkey = `${p}/${s}`; const obj = { node: { title: s, key: p ? vkey : s, parent: p, }, }; setExpandedKeys([p]); onTreeSelect([vkey], obj); } }; const onSelect = (value: any, node: any) => { setSelect(node.key); setCurrentNode(node); if (node.key === select || !value) { return; } if (node.type === 'directory') { setValue('请选择脚本文件'); return; } const newMode = value ? LangMap[value.slice(-3)] : ''; setMode(isPhone && newMode === 'typescript' ? 'javascript' : newMode); setValue('加载中...'); getDetail(node); }; const onTreeSelect = useCallback( (keys: Key[], e: any) => { const content = editorRef.current ? editorRef.current.getValue().replace(/\r\n/g, '\n') : value; if (content !== value) { Modal.confirm({ title: `确认离开`, content: <>当前修改未保存,确定离开吗, onOk() { onSelect(keys[0], e.node); setIsEditing(false); }, onCancel() { console.log('Cancel'); }, }); } else { setIsEditing(false); onSelect(keys[0], e.node); } }, [value], ); const onSearch = useCallback( (e) => { const keyword = e.target.value; debounceSearch(keyword); }, [data], ); const debounceSearch = useCallback( debounce((keyword) => { setSearchValue(keyword); }, 300), [data], ); const { treeData: filterData, keys: searchExpandedKeys } = useFilterTreeData( data, searchValue, { treeNodeFilterProp: 'title' }, ); useEffect(() => { setExpandedKeys(uniq([...expandedKeys, ...searchExpandedKeys])); }, [searchExpandedKeys]); const onExpand = (expKeys: any) => { setExpandedKeys(expKeys); }; const editFile = () => { setTimeout(() => { setIsEditing(true); }, 300); }; const cancelEdit = () => { setIsEditing(false); setValue('加载中...'); getDetail(currentNode); }; const saveFile = () => { Modal.confirm({ title: `确认保存`, content: ( <> 确认保存文件 {currentNode.title} {' '} ,保存后不可恢复 ), onOk() { const content = editorRef.current ? editorRef.current.getValue().replace(/\r\n/g, '\n') : value; return new Promise((resolve, reject) => { request .put(`${config.apiPrefix}scripts`, { data: { filename: currentNode.title, path: currentNode.parent || '', content, }, }) .then(({ code, data }) => { if (code === 200) { message.success(`保存成功`); setValue(content); setIsEditing(false); } resolve(null); }) .catch((e) => reject(e)); }); }, onCancel() { console.log('Cancel'); }, }); }; const deleteFile = () => { Modal.confirm({ title: `确认删除`, content: ( <> 确认删除 {select} 文件{currentNode.type === 'directory' ? '夹及其子文件' : ''} ,删除后不可恢复 ), onOk() { request .delete(`${config.apiPrefix}scripts`, { data: { filename: currentNode.title, path: currentNode.parent || '', type: currentNode.type, }, }) .then(({ code }) => { if (code === 200) { message.success(`删除成功`); let newData = [...data]; if (currentNode.parent) { newData = depthFirstSearch( newData, (c) => c.key === currentNode.key, ); } else { const index = newData.findIndex( (x) => x.key === currentNode.key, ); if (index !== -1) { newData.splice(index, 1); } } setData(newData); initState(); } }); }, onCancel() { console.log('Cancel'); }, }); }; const addFile = () => { setIsAddFileModalVisible(true); }; const addFileModalClose = ( { filename, path, key }: { filename: string; path: string; key: string } = { filename: '', path: '', key: '', }, ) => { if (filename) { let newData = [...data]; const _file = { title: filename, key, parent: path }; if (path) { newData = depthFirstSearch(newData, (c) => c.key === path, _file); const keys = path.split('/'); const sKeys: string[] = []; keys.reduce((p, c) => { sKeys.push(p); return `${p}/${c}`; }); setExpandedKeys([...expandedKeys, ...sKeys, path]); } else { newData.unshift(_file); } setData(newData); onSelect(_file.title, _file); setIsEditing(true); } setIsAddFileModalVisible(false); }; const downloadFile = () => { request .post(`${config.apiPrefix}scripts/download`, { data: { filename: currentNode.title, }, }) .then(({ code, data }) => { if (code === 200) { const blob = new Blob([data], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = currentNode.title; document.documentElement.appendChild(a); a.click(); document.documentElement.removeChild(a); } }); }; const initState = () => { setSelect(''); setCurrentNode(null); setValue('请选择脚本文件'); }; useEffect(() => { getScripts(); }, []); useEffect(() => { if (treeDom.current) { setHeight(treeDom.current.clientHeight); } }, [treeDom.current, data]); const action = (key: string | number) => { switch (key) { case 'save': saveFile(); break; case 'exit': cancelEdit(); break; default: break; } }; const menuAction = (key: string | number) => { switch (key) { case 'save': addFile(); break; case 'edit': editFile(); break; case 'delete': deleteFile(); break; default: break; } }; const menu = isEditing ? ( }, { label: '退出编辑', key: 'exit', icon: }, ]} onClick={({ key, domEvent }) => { domEvent.stopPropagation(); action(key); }} /> ) : ( }, { label: '编辑', key: 'edit', icon: , disabled: !select, }, { label: '删除', key: 'delete', icon: , disabled: !select, }, ]} onClick={({ key, domEvent }) => { domEvent.stopPropagation(); menuAction(key); }} /> ); return ( , , , ] : [ , ] } header={{ style: headerStyle, }} >
{!isPhone && ( /*// @ts-ignore*/
{data.length > 0 ? ( <>
) : (
)}
{ editorRef.current = editor; }} />
)} {isPhone && ( { setValue(value); }} onChange={(editor, data, value) => {}} /> )} { setIsLogModalVisible(false); }} />
); }; export default Script;