diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index 69666f34..c83a49df 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -117,6 +117,11 @@ const App: React.FC = (): React.ReactElement => { theme === Theme.Dark ? te.darkAlgorithm : te.defaultAlgorithm, + + token: { + padding: 16, + colorText: 'rgba(0, 0, 0, 0.85)', + }, }} > diff --git a/packages/components/src/common/svg/files/css.svg b/packages/components/src/common/svg/files/css.svg new file mode 100644 index 00000000..452c16a0 --- /dev/null +++ b/packages/components/src/common/svg/files/css.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/components/src/common/svg/files/html.svg b/packages/components/src/common/svg/files/html.svg new file mode 100644 index 00000000..a69ac2de --- /dev/null +++ b/packages/components/src/common/svg/files/html.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/components/src/common/svg/files/image.svg b/packages/components/src/common/svg/files/image.svg new file mode 100644 index 00000000..bcd8560e --- /dev/null +++ b/packages/components/src/common/svg/files/image.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/components/src/common/svg/files/js.svg b/packages/components/src/common/svg/files/js.svg new file mode 100644 index 00000000..0e9f58fd --- /dev/null +++ b/packages/components/src/common/svg/files/js.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/components/src/common/svg/files/unkown-file.svg b/packages/components/src/common/svg/files/unkown-file.svg new file mode 100644 index 00000000..bb380d45 --- /dev/null +++ b/packages/components/src/common/svg/files/unkown-file.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/components/src/common/svg/loader/input.svg b/packages/components/src/common/svg/loader/input.svg new file mode 100644 index 00000000..8c92aaf9 --- /dev/null +++ b/packages/components/src/common/svg/loader/input.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/components/src/common/svg/loader/output.svg b/packages/components/src/common/svg/loader/output.svg new file mode 100644 index 00000000..7ba3d02a --- /dev/null +++ b/packages/components/src/common/svg/loader/output.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/components/src/common/svg/loader/step.svg b/packages/components/src/common/svg/loader/step.svg new file mode 100644 index 00000000..12326180 --- /dev/null +++ b/packages/components/src/common/svg/loader/step.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/components/src/components/Layout/bundle-size-icon.svg b/packages/components/src/common/svg/overall/bundle-size-icon.svg similarity index 100% rename from packages/components/src/components/Layout/bundle-size-icon.svg rename to packages/components/src/common/svg/overall/bundle-size-icon.svg diff --git a/packages/components/src/components/Layout/compile-icon.svg b/packages/components/src/common/svg/overall/compile-icon.svg similarity index 100% rename from packages/components/src/components/Layout/compile-icon.svg rename to packages/components/src/common/svg/overall/compile-icon.svg diff --git a/packages/components/src/components/Layout/overall-icon.svg b/packages/components/src/common/svg/overall/overall-icon.svg similarity index 100% rename from packages/components/src/components/Layout/overall-icon.svg rename to packages/components/src/common/svg/overall/overall-icon.svg diff --git a/packages/components/src/components/Alerts/bundle.tsx b/packages/components/src/components/Alerts/bundle.tsx index 4547ebe4..dd5a578b 100644 --- a/packages/components/src/components/Alerts/bundle.tsx +++ b/packages/components/src/components/Alerts/bundle.tsx @@ -49,6 +49,10 @@ export const BundleAlertsBase: React.FC = ({ colorInfoBorder: 'none', }, }, + token: { + padding: 16, + colorText: 'rgba(0, 0, 0, 0.85)', + }, }} > = ({ before, after, filepath, className, editorProps }) => { +export const DiffViewer: React.FC = ({ + before, + after, + filepath, + className, + editorProps, +}) => { return ( span:not(.#{$treePrefixCls}-switcher), + &-treenode-disabled > span:not(.#{$treePrefixCls}-switcher), > a, > a span { color: #767676; cursor: not-allowed; - } + } - &-treenode-selected { - background: rgba(5, 145, 255, 0.3); - border-radius: 4px; - // &-node-selected - } + &-treenode-selected { + background: rgba(5, 145, 255, 0.3); + border-radius: 4px; + // &-node-selected + } - &-title { - display: inline-block; - } + &-title { + display: inline-block; + } - &-indent { - display: inline-block; - height: 0; - vertical-align: bottom; - } - &-indent-unit { - display: inline-block; - width: 14px; + &-indent { + display: inline-block; + height: 0; + vertical-align: bottom; + } + &-indent-unit { + display: inline-block; + width: 14px; + } } } -} .file-tree-switcher-arrow { transform: rotate(0deg); @@ -70,3 +72,9 @@ $treeNodePrefixCls: '#{$treePrefixCls}-treenode'; transform: rotate(90deg); } } + +.fileIcon { + display: inline-block; + padding: 2px 0; + margin-left: 7px; +} diff --git a/packages/components/src/components/FileTree/index.tsx b/packages/components/src/components/FileTree/index.tsx index e500ebc8..e48053d3 100644 --- a/packages/components/src/components/FileTree/index.tsx +++ b/packages/components/src/components/FileTree/index.tsx @@ -1,21 +1,66 @@ import React from 'react'; import { Space, theme } from 'antd'; -import { FileOutlined, FolderOpenOutlined, FolderOutlined, RightOutlined } from '@ant-design/icons'; +import { + FileOutlined, + FolderOpenTwoTone, + FolderTwoTone, + RightOutlined, +} from '@ant-design/icons'; import Tree, { TreeProps } from 'rc-tree'; -import { Size } from '../../constants'; +import CSSIcon from 'src/common/svg/files/css.svg'; +import HtmlIcon from 'src/common/svg/files/html.svg'; +import IMGIcon from 'src/common/svg/files/image.svg'; +import JSIcon from 'src/common/svg/files/js.svg'; +import FileIcon from 'src/common/svg/files/unkown-file.svg'; import './index.sass'; import { useTheme } from '../../utils'; +import path from 'path'; const { useToken } = theme; +function getFileType(filename: string) { + const extension = path.extname(filename).slice(1).toLowerCase(); + + switch (extension) { + case 'js': + case 'jsx': + case 'ts': + return ; + case 'tsx': + return ; + case 'css': + case 'scss': + case 'sass': + case 'less': + return ; + case 'html': + return ; + case 'png': + case 'jpg': + case 'jpeg': + case 'gif': + case 'svg': + case 'bmp': + case 'webp': + return ; + default: + return ; + } +} + +export function getFileCom(filename: string) { + const fileIcon = getFileType(filename); + return
{fileIcon}
; +} + export const FileTree: React.FC> = (props) => { const { isDark } = useTheme(); const { token } = useToken(); const color = isDark ? token.colorWhite : token.colorText; const style = { fontSize: token.fontSize }; - + return ( > = (props) => { className={`file-tree-switcher-arrow ${expanded ? 'file-tree-switcher-arrow-expand' : ''}`} style={style} /> - {expanded ? ( - - ) : ( - - )} + {expanded ? : } ); } - return ; + if (data?.key && typeof data.key === 'string') { + return getFileCom(data.key); + } + return ; }} expandAction="click" {...props} diff --git a/packages/components/src/components/Keyword/index.tsx b/packages/components/src/components/Keyword/index.tsx index be626fff..fd988b01 100644 --- a/packages/components/src/components/Keyword/index.tsx +++ b/packages/components/src/components/Keyword/index.tsx @@ -1,15 +1,21 @@ -import React from 'react'; -import { Typography } from 'antd'; +import React, { type ReactNode } from 'react'; +import { Popover, Typography } from 'antd'; import { TextProps } from 'antd/es/typography/Text'; +import styles from './style.module.scss'; -export const Keyword: React.FC = ({ text, keyword, ...rest }) => { +const MAX_LENGTH = 40; + +export const Keyword: React.FC< + TextProps & { text: string; keyword: string } +> = ({ text, keyword, ...rest }) => { if (!keyword) { - return {text}; + return EllipsisText({ text, rest }); } const idx = text.indexOf(keyword); + if (idx === -1) { - return {text}; + return EllipsisText({ text, rest }); } const els: (string | React.ReactNode)[] = []; @@ -20,27 +26,60 @@ export const Keyword: React.FC = const idx = str.indexOf(keyword); if (idx > -1) { if (idx !== 0) { - els.push( - - {str.slice(0, idx)} - , - ); + els.push(EllipsisText({ text: str, els })); } - els.push( - - {keyword} - , - ); + els.push(EllipsisText({ text: str, els, marked: true })); str = str.slice(idx + keyword.length); } else { - els.push( - - {str} - , - ); + els.push(EllipsisText({ text: str, els, marked: true })); break; } } return {els}; }; + +const EllipsisText = ({ + text, + els, + marked = false, + rest, +}: { + text: string; + els?: ReactNode[]; + marked?: boolean; + rest?: Record; +}) => { + if (!text) return null; + + const textLength = text.length; + + if (textLength > MAX_LENGTH) { + const start = Math.floor((MAX_LENGTH - 3) / 2); + const end = Math.ceil((MAX_LENGTH - 3) / 2); + + return ( + +
+ + {text.slice(0, start)}...{text.slice(textLength - end)} + +
+
+ ); + } + + return ( + +
+ + {text} + +
+
+ ); +}; diff --git a/packages/components/src/components/Keyword/style.module.scss b/packages/components/src/components/Keyword/style.module.scss new file mode 100644 index 00000000..00cd5b91 --- /dev/null +++ b/packages/components/src/components/Keyword/style.module.scss @@ -0,0 +1,5 @@ +.text { + font-size: 12px; + font-weight: 400; + color: rgba(0, 0, 0, 0.85); +} diff --git a/packages/components/src/components/Layout/menus.tsx b/packages/components/src/components/Layout/menus.tsx index 8619e860..86fcee5b 100644 --- a/packages/components/src/components/Layout/menus.tsx +++ b/packages/components/src/components/Layout/menus.tsx @@ -10,9 +10,9 @@ import { Menu, MenuProps } from 'antd'; import { includes } from 'lodash-es'; import React from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import CompileIcon from './compile-icon.svg'; -import BundleSizeIcon from './bundle-size-icon.svg'; -import OverallIcon from './overall-icon.svg'; +import CompileIcon from 'src/common/svg/overall/compile-icon.svg'; +import BundleSizeIcon from 'src/common/svg/overall/bundle-size-icon.svg'; +import OverallIcon from 'src/common/svg/overall/overall-icon.svg'; import { Size } from '../../constants'; import * as OverallConstants from '../../pages/Overall/constants'; import { useI18n, hasBundle, hasCompile } from '../../utils'; diff --git a/packages/components/src/components/Loader/Analysis/files.tsx b/packages/components/src/components/Loader/Analysis/files.tsx index 5d3e1a99..3ad8a829 100644 --- a/packages/components/src/components/Loader/Analysis/files.tsx +++ b/packages/components/src/components/Loader/Analysis/files.tsx @@ -12,9 +12,12 @@ import { Table, Tooltip, Typography, + List, + Popover, } from 'antd'; import { CloseCircleOutlined } from '@ant-design/icons'; import { SDK } from '@rsdoctor/types'; +import styles from './style.module.scss'; import { ServerAPIProvider } from '../../Manifest'; import { drawerWidth, Size } from '../../../constants'; import { @@ -29,6 +32,8 @@ import { LoaderExecutions } from '../executions'; import { FileTree } from '../../FileTree'; import { Keyword } from '../../Keyword'; +const ADDITION_LOADER_NUMBER = 3; + export const LoaderFiles: React.FC<{ filetree: SDK.ServerAPI.InferResponseType; cwd: string; @@ -76,20 +81,42 @@ export const LoaderFiles: React.FC<{ cwd, fileTitle(file, basename) { const { loaders, layer } = filetree.find((e) => e.path === file)!; + const additionalLoaders: (Pick< + SDK.LoaderTransformData, + 'path' | 'loader' | 'errors' + > & { costs: number })[] = []; + loaders.forEach( + (l, i) => i > ADDITION_LOADER_NUMBER && additionalLoaders.push(l), + ); + return ( { setLoaderIndex(0); setResourcePath(file); setDrawerVisible(true); }} > - - +
+
+ +
+
+ +
+
{layer ? ( {layer} @@ -97,39 +124,90 @@ export const LoaderFiles: React.FC<{ ) : ( <> )} - {loaders.map((e, i) => { + {loaders.slice(0, ADDITION_LOADER_NUMBER).map((e, i) => { const isError = e.errors && e.errors.length; const key = `${file}_${e.loader}_${i}`; - return ( - - { - ev.stopPropagation(); - setResourcePath(file); - setLoaderIndex(i); - setDrawerVisible(true); - }} - > - - {e.loader} - - - {isError ? ( - - ) : ( - - {formatCosts(e.costs)} + if (i <= ADDITION_LOADER_NUMBER) { + return ( + +
+ { + ev.stopPropagation(); + setResourcePath(file); + setLoaderIndex(i); + setDrawerVisible(true); + }} + > + + {e.loader.match(/([^/]+-loader)/g)?.[0] || e.loader} + + + {isError ? ( + + ) : ( + + {formatCosts(e.costs)} + + )} - )} - - - ); +
+
+ ); + } })} + {additionalLoaders?.length ? ( + { + const isError = e.errors && e.errors.length; + const key = `${file}_${e.loader}_${i + ADDITION_LOADER_NUMBER}`; + + return ( + + +
+ { + ev.stopPropagation(); + setResourcePath(file); + setLoaderIndex(i); + setDrawerVisible(true); + }} + > + + {e.loader.match(/([^/]+-loader)/g)?.[0] || + e.loader} + + + + {formatCosts(e.costs)} + + +
+
+
+ ); + }} + /> + } + > +
+ ··· +
+
+ ) : ( + <> + )}
); }, @@ -141,14 +219,29 @@ export const LoaderFiles: React.FC<{ return ( - + - Total Files: {filteredFiles.length} - + + Files + + Total Files: {filteredFiles.length} + + } - bodyStyle={{ overflow: 'scroll', maxHeight, height: '40rem' }} + bodyStyle={{ + overflow: 'scroll', + maxHeight, + height: '40rem', + padding: 14, + }} > - {selectedNode ? ( - - setSelectedNode(null)} + maskClosable + width={drawerWidth} + zIndex={999} + bodyStyle={{ padding: 0 }} + > + {selectedNode && ( + + + + + {`Statistics of`} + {selectedNode.title as React.ReactNode} + + + } > - - Statistics of "{selectedNode.title as React.ReactNode}" - - - } - > - - {(tableData) => ( - e.loader} - columns={[ - { - title: 'Loader Name', - dataIndex: 'loader', - }, - { - title: 'Files', - dataIndex: 'files', - }, - { - title: 'Total Duration', - dataIndex: 'costs', - render: (v) => ( - - {formatCosts(v)} - - ), - sorter: (a, b) => a.costs - b.costs, - defaultSortOrder: 'descend', - sortDirections: ['descend', 'ascend'], - }, - ]} - dataSource={tableData!} - footer={() => ( - + {(tableData) => ( +
- - {tableData!.length} - - - {sumBy(tableData, (e) => e.files)} - - - - {formatCosts(sumBy(tableData, (e) => e.costs))} - - - + rowKey={(e) => e.loader} + columns={[ + { + title: 'Loader Name', + dataIndex: 'loader', + }, + { + title: 'Files', + dataIndex: 'files', + }, + { + title: 'Total Duration', + dataIndex: 'costs', + render: (v) => ( + + {formatCosts(v)} + + ), + sorter: (a, b) => a.costs - b.costs, + defaultSortOrder: 'descend', + sortDirections: ['descend', 'ascend'], + }, + ]} + dataSource={tableData!} + footer={() => ( + + + {tableData!.length} + + + {sumBy(tableData, (e) => e.files)} + + + + {formatCosts(sumBy(tableData, (e) => e.costs))} + + + + )} + /> )} - /> - )} - - - - ) : null} + + + + + )} + ); }; diff --git a/packages/components/src/components/Loader/Analysis/index.tsx b/packages/components/src/components/Loader/Analysis/index.tsx index 8e6dd306..d2a7bfc4 100644 --- a/packages/components/src/components/Loader/Analysis/index.tsx +++ b/packages/components/src/components/Loader/Analysis/index.tsx @@ -3,6 +3,9 @@ import { SDK } from '@rsdoctor/types'; import { ServerAPIProvider, withServerAPI } from '../../Manifest'; import { LoaderFiles } from './files'; import { ISelectLoaderProps, LoaderCommonSelect } from '../../Select'; +import { ConfigContext } from 'src/config'; +import { ConfigProvider } from 'antd'; +import { getLocale } from 'src/utils'; export const LoaderAnalysisBase: React.FC<{ cwd: string; @@ -14,27 +17,43 @@ export const LoaderAnalysisBase: React.FC<{ } as ISelectLoaderProps); return ( -
- - {(loaderNames) => ( - setStore(e)} - loaderNames={loaderNames} - /> - )} - - - {(filetree) => ( - - )} - -
+ + {(v) => { + return ( + +
+ + {(loaderNames) => ( + setStore(e)} + loaderNames={loaderNames} + /> + )} + + + {(filetree) => ( + + )} + +
+
+ ); + }} +
); }; diff --git a/packages/components/src/components/Loader/Analysis/style.module.scss b/packages/components/src/components/Loader/Analysis/style.module.scss new file mode 100644 index 00000000..a68dd71c --- /dev/null +++ b/packages/components/src/components/Loader/Analysis/style.module.scss @@ -0,0 +1,87 @@ +.divider { + border-color: rgba(0, 0, 0, 0.12); + position: relative; + bottom: 20%; +} + +.dividerDiv { + flex-grow: 1; + margin-left: 10px; +} + +.box { + width: 260px; + display: flex; + height: 40px; + + .keyword { + flex: 0 0 auto; + padding-right: 5; + } +} + +.textBox { + max-width: 220px; + color: rgba(0, 0, 0, 0.85); + height: 22px; + border: 1px #d9d9d9 solid; + justify-content: center; + display: flex; + padding: 1px 8px 1px 8px; + align-items: center; + border-radius: 2px; + background-color: #fafafa; + + .text { + font-size: 12px; + font-weight: 400; + } +} + +:global { + .rc-tree .rc-tree-treenode { + line-height: 30px; + } +} + +.space { + :global { + .ant-space-item { + display: flex; + } + } +} + +.executions .timeline { + .box { + border: 1px solid #eaedf1; + border-radius: 8px; + width: 80%; + height: 74px; + display: flex; + flex-direction: column; + padding: 16px; + + &:active { + background-color: #f2f8ff; + border: 1px solid #0079ff; + } + + :global { + .ant-typography { + color: rgba(0, 0, 0, 0.65); + font-size: 14px; + } + } + .loader { + color: inherit; + font-size: 14px; + height: 72px; + align-content: center; + } + } + .selected { + background-color: #f2f8ff; + border: 1px solid #0079ff; + } +} diff --git a/packages/components/src/components/Loader/executions.tsx b/packages/components/src/components/Loader/executions.tsx index 13b43759..a548fe0c 100644 --- a/packages/components/src/components/Loader/executions.tsx +++ b/packages/components/src/components/Loader/executions.tsx @@ -1,9 +1,7 @@ -import { ClockCircleOutlined } from '@ant-design/icons'; +import { ClockCircleTwoTone } from '@ant-design/icons'; import Editor from '@monaco-editor/react'; import { SDK } from '@rsdoctor/types'; import { - Badge, - Button, Col, Divider, Empty, @@ -14,11 +12,14 @@ import { Tooltip, Typography, Tabs, + List, } from 'antd'; -import type { EllipsisConfig } from 'antd/lib/typography/Base'; -import type { TextProps } from 'antd/lib/typography/Text'; import dayjs from 'dayjs'; import { PropsWithChildren, useCallback, useState } from 'react'; + +import StepIcon from 'src/common/svg/loader/step.svg'; +import InputIcon from 'src/common/svg/loader/input.svg'; +import OutputIcon from 'src/common/svg/loader/output.svg'; import { Size } from '../../constants'; import { beautifyPath, @@ -30,6 +31,7 @@ import { Card } from '../Card'; import { DiffViewer } from '../CodeViewer'; import { CodeOpener } from '../Opener'; import { Title } from '../Title'; +import styles from './Analysis/style.module.scss'; interface LoaderExecutionsProps { cwd: string; @@ -37,38 +39,6 @@ interface LoaderExecutionsProps { index?: number; } -const LoaderInfoItem = ({ - label, - value, - ellipsis = false, - ...textProps -}: { - label: string; - value: string | JSX.Element; - ellipsis?: EllipsisConfig; -} & TextProps): JSX.Element => { - return ( - - - - -
- - {label} - -
- -
- - - - {value} - - - - ); -}; - const LoaderPropsItem = ({ loader, resource, @@ -85,51 +55,61 @@ const LoaderPropsItem = ({ title={'Loader Details'} style={{ border: 'none' }} extra={ - } color="default"> + } color="default"> {dayjs(loader.startAt).format('YYYY-MM-DD HH:mm:ss')} } > {loader.isPitch ? pitch : null} - - - } - /> - - - - - } - /> - - + + + + {'File Path'} +
{beautifyPath(resource.path, cwd)}
+
+ + {'Resource Path'} + + + + {'Resource Query'} +
{resource.queryRaw || '-'}
+
+ + {'Duration'} +
{formatCosts(loader.costs)}
+
+ + {'Loader'} +
+ {loader.loader} +
+
+ + {'Loader Index'} +
{`${loader.loaderIndex}`}
+
+ + {'Loader Path'} + + + +
+ {'Options'} +
+ + + {JSON.stringify(loader.options || '-')} + +
+
); }; @@ -154,7 +134,7 @@ export const LoaderExecutions = ({ }, []); return ( - +
{loaders.map((e, i, arr) => { const { loader, isPitch } = e; + const costs = formatCosts(e.costs); return ( ) : ( - + ) } style={{ paddingBottom: 10, textAlign: 'center' }} key={i} > - + - + {i === arr.length - 1 ? null : (
- ⬇️ +
)} @@ -257,7 +240,7 @@ export const LoaderExecutions = ({
- Input + + + Input +
- Output + + + Output +
diff --git a/packages/components/src/pages/Overall/index.module.scss b/packages/components/src/pages/Overall/index.module.scss index 1146248e..237d15c1 100644 --- a/packages/components/src/pages/Overall/index.module.scss +++ b/packages/components/src/pages/Overall/index.module.scss @@ -8,8 +8,12 @@ margin-left: 7px; } - .rc-tree .rc-tree-treenode .rc-tree-node-content-wrapper { - cursor: default; + .rc-tree .rc-tree-treenode { + line-height: 30px; + + .rc-tree-node-content-wrapper { + cursor: default; + } } // for bundle overall diff --git a/packages/components/src/pages/WebpackLoaders/Analysis/index.tsx b/packages/components/src/pages/WebpackLoaders/Analysis/index.tsx index 34f93118..b1f68076 100644 --- a/packages/components/src/pages/WebpackLoaders/Analysis/index.tsx +++ b/packages/components/src/pages/WebpackLoaders/Analysis/index.tsx @@ -2,13 +2,17 @@ import React from 'react'; import { LoaderAnalysis } from '../../../components/Loader/Analysis'; import { WebpackConfigurationViewer } from '../../../components/Configuration'; import { Card } from '../../../components/Card'; +import { theme } from 'antd'; +const { useToken } = theme; export const Page: React.FC = () => { + const { token } = useToken(); + return ( } - bodyStyle={{ paddingTop: 0, height: 800 }} + bodyStyle={{ paddingTop: token.padding, height: 800 }} >