-
+
- {networkTitle.map((t) => {
- return {t} | ;
+ {mergedColumns.map((t) => {
+ return ;
})}
-
- {data.length > 0 ? (
-
-
- {data.map((row, index) => {
+
+ {data.length > 0 ? (
+ data.map((row, index) => {
return (
{
})}
>
{
setActiveIndex(index);
- // setDetailData(row);
setLeftDistance(evt.target.clientWidth);
}}
>
@@ -232,27 +298,24 @@ export const NetworkTable = ({ data, cookie }: NetworkTableProps) => {
);
- })}
- |
-
- ) : (
-
- )}
+ })
+ ) : (
+
+ )}
+
+
- {showDetail && detailData && (
+ {detailData && (
{
- detailClicked.current = true;
- }}
>
{
- setShowDetail(false);
+ setActiveIndex(-1);
}}
/>
diff --git a/src/components/NetworkTable/utils.ts b/src/components/NetworkTable/utils.ts
index 159eb00e..c4f8b715 100644
--- a/src/components/NetworkTable/utils.ts
+++ b/src/components/NetworkTable/utils.ts
@@ -1,5 +1,4 @@
-import { getObjectKeys } from '@/utils';
-import { SpyNetwork } from '@huolala-tech/page-spy-types';
+import { getObjectKeys, ResolvedNetworkInfo } from '@/utils';
export function downloadFile(filename: string, url: string) {
const aTag = document.createElement('a');
@@ -39,7 +38,7 @@ export function semanticSize(size: number) {
return `${(size / oneMB).toFixed(1)} MB`;
}
-export function getStatusText(row: SpyNetwork.RequestInfo) {
+export function getStatusText(row: ResolvedNetworkInfo) {
if (row.readyState === 0 || row.readyState === 1) return 'Pending';
if (row.readyState === 4) {
if (row.status === 0) return 'Failed';
diff --git a/src/components/ResizableTitle/cache-key.ts b/src/components/ResizableTitle/cache-key.ts
new file mode 100644
index 00000000..8920e224
--- /dev/null
+++ b/src/components/ResizableTitle/cache-key.ts
@@ -0,0 +1,7 @@
+export const ONLINE_NETWORK_CACHE = 'online:network-table-resize-config';
+export const OFFLINE_NETWORK_CACHE = 'offline:network-table-resize-config';
+
+export const ONLINE_STORAGE_CACHE = 'online:storage-table-resize-config';
+export const OFFLINE_STORAGE_CACHE = 'offline:storage-table-resize-config';
+
+export const ONLINE_DB_CACHE = 'online:db-table-resize-config';
diff --git a/src/components/ResizableTitle/index.less b/src/components/ResizableTitle/index.less
new file mode 100644
index 00000000..f3882c17
--- /dev/null
+++ b/src/components/ResizableTitle/index.less
@@ -0,0 +1,13 @@
+.resizable-title {
+ position: relative;
+ background-clip: padding-box;
+ .resizable-handle {
+ position: absolute;
+ right: -5px;
+ bottom: 0;
+ z-index: 1;
+ width: 10px;
+ height: 100%;
+ cursor: col-resize;
+ }
+}
diff --git a/src/components/ResizableTitle/index.tsx b/src/components/ResizableTitle/index.tsx
new file mode 100644
index 00000000..974617b8
--- /dev/null
+++ b/src/components/ResizableTitle/index.tsx
@@ -0,0 +1,57 @@
+import { HTMLAttributes } from 'react';
+import { Resizable, ResizableProps } from 'react-resizable';
+import './index.less';
+
+export type ResizableTitleProps = HTMLAttributes
& {
+ onResizeStart: ResizableProps['onResizeStart'];
+ onResize: ResizableProps['onResize'];
+ onResizeStop: ResizableProps['onResizeStop'];
+ width?: number;
+ widthConstraints?: number[];
+};
+
+export const WIDTH_CONSTRAINTS = [60, 500];
+
+export const ResizableTitle = (props: ResizableTitleProps) => {
+ const {
+ onResize,
+ onResizeStart,
+ onResizeStop,
+ width,
+ widthConstraints = WIDTH_CONSTRAINTS,
+ ...rest
+ } = props;
+
+ if (!width) return | ;
+
+ const minConstraints: ResizableProps['minConstraints'] = [
+ widthConstraints[0],
+ 0,
+ ];
+ const maxConstraints: ResizableProps['maxConstraints'] = [
+ widthConstraints[1],
+ 0,
+ ];
+ return (
+ {
+ e.stopPropagation();
+ }}
+ />
+ }
+ onResize={onResize}
+ onResizeStart={onResizeStart}
+ onResizeStop={onResizeStop}
+ >
+ |
+
+ );
+};
diff --git a/src/components/StorageTable/index.tsx b/src/components/StorageTable/index.tsx
index da234529..943d2c7f 100644
--- a/src/components/StorageTable/index.tsx
+++ b/src/components/StorageTable/index.tsx
@@ -3,16 +3,82 @@ import { StorageType } from '@/store/platform-config';
import { SpyStorage } from '@huolala-tech/page-spy-types';
import { Table, Tooltip } from 'antd';
import { capitalize } from 'lodash-es';
-import { useMemo } from 'react';
+import { useMemo, useRef, useState } from 'react';
+import { ResizableTitle } from '../ResizableTitle';
+import { ResizeCallbackData } from 'react-resizable';
+import { ColumnType } from 'antd/es/table/interface';
-const { Column } = Table;
+const allCols = [
+ {
+ dataIndex: 'name',
+ title: 'Name',
+ ellipsis: true,
+ width: 200,
+ },
+ {
+ dataIndex: 'value',
+ title: 'Value',
+ ellipsis: true,
+ width: 300,
+ },
+ {
+ dataIndex: 'domain',
+ title: 'Domain',
+ ellipsis: true,
+ width: 200,
+ },
+ {
+ dataIndex: 'path',
+ title: 'Path',
+ ellipsis: true,
+ width: 100,
+ },
+ {
+ dataIndex: 'expires',
+ title: 'Expires',
+ ellipsis: true,
+ width: 240,
+ render: (value: string) => {
+ const time = value ? new Date(value).toISOString() : 'Session';
+ return (
+
+ {time}
+
+ );
+ },
+ },
+ {
+ dataIndex: 'secure',
+ title: 'Secure',
+ ellipsis: true,
+ width: 80,
+ render: (bool: boolean) => bool && '✅',
+ },
+ {
+ dataIndex: 'sameSite',
+ title: 'SameSite',
+ ellipsis: true,
+ width: 80,
+ render: (v: string) => capitalize(v),
+ },
+ {
+ dataIndex: 'partitioned',
+ title: 'Partitioned',
+ ellipsis: true,
+ },
+];
interface Props {
activeTab: StorageType;
storageMsg: Record;
+ resizeCacheKey: string;
}
-export const StorageTable = ({ activeTab, storageMsg }: Props) => {
+export const StorageTable = ({
+ activeTab,
+ storageMsg,
+ resizeCacheKey,
+}: Props) => {
const data = useMemo(() => {
return Object.values(storageMsg[activeTab]);
}, [activeTab, storageMsg]);
@@ -21,16 +87,89 @@ export const StorageTable = ({ activeTab, storageMsg }: Props) => {
return Object.keys(rest).length > 0;
}, [data]);
+ const unionCacheKey = useMemo(
+ () => `${resizeCacheKey}:${activeTab}`,
+ [resizeCacheKey, activeTab],
+ );
+ const cacheWidthRef = useRef>({});
+
+ const [columns, setColumns] =
+ useState[]>(allCols);
+
+ // Init width from cache
+ useMemo(() => {
+ const cache = localStorage.getItem(unionCacheKey);
+ const value = cache && JSON.parse(cache);
+ if (value) {
+ cacheWidthRef.current[unionCacheKey] = value;
+ const widthInitedColumns = [...allCols].map((i) => {
+ return {
+ ...i,
+ width: value[i.title] || i.width,
+ };
+ });
+ setColumns(widthInitedColumns);
+ } else {
+ cacheWidthRef.current[unionCacheKey] = {};
+ }
+ }, [unionCacheKey]);
+
+ // Dragging
+ const mergedColumns = useMemo(() => {
+ let renderCols = [...columns];
+ if (!hasDetail && renderCols.length !== 2) {
+ renderCols = renderCols.slice(0, 2);
+ }
+ const cacheWidth = cacheWidthRef.current[unionCacheKey];
+ if (cacheWidth) {
+ renderCols.forEach((i) => {
+ const width = cacheWidth[i.title as string];
+ if (width) {
+ i.width = width;
+ }
+ });
+ }
+
+ return renderCols.map((c, index) => ({
+ ...c,
+ onHeaderCell: (column: ColumnType) => ({
+ width: column.width,
+ onResize: ((
+ _: React.SyntheticEvent,
+ data: ResizeCallbackData,
+ ) => {
+ const { size } = data;
+
+ const newCols = [...renderCols];
+ newCols[index] = {
+ ...newCols[index],
+ width: size.width,
+ };
+ setColumns(newCols);
+
+ cacheWidthRef.current[unionCacheKey][column.title as string] =
+ size.width;
+ }) as React.ReactEventHandler,
+ onResizeStop: (_: React.SyntheticEvent, data: ResizeCallbackData) => {
+ const refValue = cacheWidthRef.current[unionCacheKey];
+
+ localStorage.setItem(unionCacheKey, JSON.stringify(refValue));
+ },
+ }),
+ }));
+ }, [columns, hasDetail, unionCacheKey]);
+
const setDetailInfo = useCacheDetailStore((state) => state.setCurrentDetail);
return (
{
return {
onClick() {
@@ -38,42 +177,11 @@ export const StorageTable = ({ activeTab, storageMsg }: Props) => {
},
};
}}
- >
-
-
- {hasDetail && (
- <>
-
-
- {
- const time = value ? new Date(value).toISOString() : 'Session';
- return (
-
- {time}
-
- );
- }}
- />
- bool && '✅'}
- />
- capitalize(v)}
- />
-
- >
- )}
-
+ components={{
+ header: {
+ cell: ResizableTitle,
+ },
+ }}
+ />
);
};
diff --git a/src/pages/Devtools/NetworkPanel/index.tsx b/src/pages/Devtools/NetworkPanel/index.tsx
index 531a3ba5..a7560306 100644
--- a/src/pages/Devtools/NetworkPanel/index.tsx
+++ b/src/pages/Devtools/NetworkPanel/index.tsx
@@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next';
import { useSocketMessageStore } from '@/store/socket-message';
import { NetworkTable } from '@/components/NetworkTable';
import { useForceThrottleRender } from '@/utils/useForceRender';
+import { ONLINE_NETWORK_CACHE } from '@/components/ResizableTitle/cache-key';
const NetworkPanel = memo(() => {
const { t: ct } = useTranslation('translation', { keyPrefix: 'common' });
@@ -42,6 +43,7 @@ const NetworkPanel = memo(() => {