From d7aaf9ff8610f2d67b0744617dd13716c8e89cbb Mon Sep 17 00:00:00 2001 From: Maricaya <915270549@qq.com> Date: Sat, 28 Sep 2024 12:19:18 -0500 Subject: [PATCH] feat: edit the front-end page --- core/src/auth.rs | 8 +- core/src/client.rs | 4 +- frontend/package.json | 7 + frontend/postcss.config.js | 6 + frontend/public/logo.svg | 16 + frontend/src/ProfileGraphDashboard.tsx | 326 ++++++++--------- frontend/src/components/Attributes.tsx | 22 +- frontend/src/components/FlowAnalysisGraph.tsx | 78 ++++ .../src/components/MostExpensiveNodes.tsx | 27 +- frontend/src/components/ProfileOverview.tsx | 18 +- .../src/components/ProfileOverviewNode.tsx | 15 +- frontend/src/components/Statistics.tsx | 17 +- frontend/src/constants/index.ts | 41 +++ .../{less => css}/ProfileGraphDashboard.css | 9 +- .../src/hooks/useFlowAnalysisGraphConfig.tsx | 243 +++++++++++++ frontend/src/hooks/useFlowGraphConfig.ts | 177 ---------- frontend/src/hooks/useGraphEvents.ts | 99 ++++++ frontend/src/hooks/useGraphSize.ts | 34 ++ frontend/src/hooks/useNodeSelection.ts | 66 ++++ frontend/src/hooks/useProfileData.ts | 160 +++++++++ frontend/src/hooks/useRefValueListener.ts | 16 + frontend/src/hooks/useReshape.ts | 40 +++ frontend/src/images/icons/download.svg | 1 + frontend/src/images/icons/full-screen.svg | 1 + frontend/src/images/icons/zoom-in.svg | 1 + frontend/src/images/icons/zoom-out.svg | 1 + frontend/src/index.css | 15 +- frontend/src/index.tsx | 1 + frontend/src/types/ProfileGraphDashboard.ts | 55 ++- frontend/src/utills/graph.ts | 197 +++++++++++ frontend/src/utills/index.ts | 58 +++ frontend/tailwind.config.js | 11 + frontend/tsconfig.json | 1 + frontend/yarn.lock | 332 ++++++++++++------ 34 files changed, 1567 insertions(+), 536 deletions(-) create mode 100644 frontend/postcss.config.js create mode 100644 frontend/public/logo.svg create mode 100644 frontend/src/components/FlowAnalysisGraph.tsx create mode 100644 frontend/src/constants/index.ts rename frontend/src/{less => css}/ProfileGraphDashboard.css (92%) create mode 100644 frontend/src/hooks/useFlowAnalysisGraphConfig.tsx delete mode 100644 frontend/src/hooks/useFlowGraphConfig.ts create mode 100644 frontend/src/hooks/useGraphEvents.ts create mode 100644 frontend/src/hooks/useGraphSize.ts create mode 100644 frontend/src/hooks/useNodeSelection.ts create mode 100644 frontend/src/hooks/useProfileData.ts create mode 100644 frontend/src/hooks/useRefValueListener.ts create mode 100644 frontend/src/hooks/useReshape.ts create mode 100644 frontend/src/images/icons/download.svg create mode 100644 frontend/src/images/icons/full-screen.svg create mode 100644 frontend/src/images/icons/zoom-in.svg create mode 100644 frontend/src/images/icons/zoom-out.svg create mode 100644 frontend/src/utills/graph.ts create mode 100644 frontend/src/utills/index.ts create mode 100644 frontend/tailwind.config.js diff --git a/core/src/auth.rs b/core/src/auth.rs index 09ad359b7..a85c0001b 100644 --- a/core/src/auth.rs +++ b/core/src/auth.rs @@ -17,12 +17,12 @@ use reqwest::RequestBuilder; use crate::error::{Error, Result}; #[async_trait::async_trait] -pub trait Auth: Sync + Send + std::fmt::Debug { +pub trait Auth: Sync + Send { async fn wrap(&self, builder: RequestBuilder) -> Result; fn username(&self) -> String; } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct BasicAuth { username: String, password: SensitiveString, @@ -48,7 +48,7 @@ impl Auth for BasicAuth { } } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct AccessTokenAuth { token: SensitiveString, } @@ -72,7 +72,7 @@ impl Auth for AccessTokenAuth { } } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct AccessTokenFileAuth { token_file: String, } diff --git a/core/src/client.rs b/core/src/client.rs index a95d8ccb2..ca4c3e70d 100644 --- a/core/src/client.rs +++ b/core/src/client.rs @@ -47,7 +47,7 @@ static VERSION: Lazy = Lazy::new(|| { version.to_string() }); -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct APIClient { pub cli: HttpClient, pub scheme: String, @@ -385,8 +385,6 @@ impl APIClient { } resp.schema = schema; resp.data = data; - // resp.error = None; - println!("----resp:--- ==={:?}", resp.error); Ok(resp) } else { Ok(resp) diff --git a/frontend/package.json b/frontend/package.json index f78f40381..7dbf54940 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "antd": "^5.19.1", + "lodash-es": "^4.17.21", "react": "^18.3.1", "react-dom": "^18.3.1", "react-scripts": "5.0.1", @@ -41,5 +42,11 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/lodash-es": "^4.17.12", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.13" } } diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 000000000..33ad091d2 --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/public/logo.svg b/frontend/public/logo.svg new file mode 100644 index 000000000..ab3b97498 --- /dev/null +++ b/frontend/public/logo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/frontend/src/ProfileGraphDashboard.tsx b/frontend/src/ProfileGraphDashboard.tsx index fa1a743c3..44fb3b39c 100644 --- a/frontend/src/ProfileGraphDashboard.tsx +++ b/frontend/src/ProfileGraphDashboard.tsx @@ -1,205 +1,171 @@ -import React, { useEffect, useState } from "react"; +import React, { useRef, useState } from "react"; import { Layout } from "antd"; - -import { FlowAnalysisGraph } from "@ant-design/graphs"; +import { IGraph } from "@ant-design/charts"; import MostExpensiveNodes from './components/MostExpensiveNodes'; import ProfileOverview from './components/ProfileOverview'; import ProfileOverviewNode from './components/ProfileOverviewNode'; import Statistics from './components/Statistics'; import Attributes from './components/Attributes'; +import FlowAnalysisGraph from './components/FlowAnalysisGraph'; -import { useFlowGraphConfig } from './hooks/useFlowGraphConfig'; +import { useProfileData } from "./hooks/useProfileData"; +import { useGraphSize } from "./hooks/useGraphSize"; +import { useGraphEvents } from "./hooks/useGraphEvents"; +import { useNodeSelection } from "./hooks/useNodeSelection"; -import { GraphData, ProfileData, StatisticsData, AttributeData, StatisticsDesc, Profile, MessageResponse } from './types/ProfileGraphDashboard'; +import { IGraphSize, IOverview, Profile } from "./types/ProfileGraphDashboard"; +import { ALL_NODE_ID } from "./constants"; -import "./less/ProfileGraphDashboard.css"; +import "./css/ProfileGraphDashboard.css"; const { Content, Sider } = Layout; function ProfileGraphDashboard() { - const [graphData, setGraphData] = useState({ nodes: [], edges: [] }); - - const [profileDataArray, setProfileDataArray] = useState([]); - - const [selectedNodeId, setSelectedNodeId] = useState("all"); - - const [statisticsData, setStatisticsData] = useState([]); - - const [labels, setLabels] = useState([]); - - const handleNodeSelection = (nodeId: string) => { - setSelectedNodeId(nodeId); - }; - - const flowGraphConfig = useFlowGraphConfig({ - graphData, - onNodeClick: handleNodeSelection, - }); - - - useEffect(() => { - const fetchMessage = async () => { - try { - const response: Response = await fetch("/api/message"); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - const result: MessageResponse = await response.json(); - - const data_json: { - profiles: Profile[]; - statistics_desc: StatisticsDesc[]; - } = JSON.parse(result?.result); - // setGraphData(data); - const all_statistics_sum = data_json.profiles - .map((profile: any) => profile.statistics[0] + profile.statistics[1]) - .reduce((acc: number, curr: number) => acc + curr, 0); - - const all_cpu_time = data_json.profiles - .map((profile: any) => profile.statistics[0]) - .reduce((acc: number, curr: number) => acc + curr, 0); - - const all_io_time = data_json.profiles - .map((profile: any) => profile.statistics[1]) - .reduce((acc: number, curr: number) => acc + curr, 0); - - const allprofileData = { - id: "all", - totalExecutionTime: parseFloat( - (all_statistics_sum / 1_000_00).toFixed(2) - ), - cpuTimePercentage: parseFloat( - ((all_cpu_time / all_statistics_sum) * 100).toFixed(2) - ), - ioTimePercentage: parseFloat( - ((all_io_time / all_statistics_sum) * 100).toFixed(2) - ), - } - - const profileDataArray = data_json.profiles.map((profile: any) => { - const totalExecutionTime = profile.statistics[0] + profile.statistics[1]; - const cpuTimePercentage = profile.statistics[0] / totalExecutionTime; - const ioTimePercentage = profile.statistics[1] / totalExecutionTime; - return{ - id: profile.id.toString(), - totalExecutionTime: 0, - cpuTimePercentage: parseFloat( - (cpuTimePercentage*100).toFixed(2) - ), - ioTimePercentage: parseFloat( - (ioTimePercentage*100).toFixed(2) - ), - } - }); - - profileDataArray.push(allprofileData); - - setProfileDataArray(profileDataArray); - - const statisticsDesc = data_json.statistics_desc; - const statsArray = data_json.profiles.map((profile: any) => { - const statistics = Object.entries(statisticsDesc).map(([key, value]) => { - const index = value.index; - return { - name: value.display_name || key, - desc: value.desc, - value: profile.statistics[index], - unit: value.unit, - }; - }); - return {statistics, id: profile.id.toString()}; - }); - - setStatisticsData(statsArray); - - const nodes = data_json.profiles.map((profile: any) => ({ - id: profile.id.toString(), - value: { - items: [ - { - id: profile.id.toString(), - name: profile.name, - title: profile.title, - progress: Math.round( - (profile.statistics[0] / all_statistics_sum) * 100 - ), - cpuTime: profile.statistics[0], - ioTime: profile.statistics[1], - text: profile.title, - }, - ], - }, - })); - - const edges = data_json.profiles - .filter((profile: any) => profile.parent_id !== null) - .map((profile: any) => ({ - target: profile.parent_id.toString(), - source: profile.id.toString(), - })); - const data = { - nodes, - edges, - }; - setGraphData(data); - - const labels = data_json.profiles.map((profile: any) => { - return { - labels: profile.labels, - id: profile.id.toString(), - } - }); - setLabels(labels); - } catch (error) { - console.error("Error fetching message:", error); - } - }; + const [selectedNodeId, setSelectedNodeId] = useState(ALL_NODE_ID); + + const profileWrapRefCanvas = useRef(null); + + const profileWrapRef = useRef(null); + const graphRef = useRef(null); + + const { graphSize, profileRef, handleResize } = useGraphSize(); + + const { + plainData, + rangeData, + statisticsData, + labels, + overviewInfo, + isLoading, + setOverviewInfo, + setIsLoading, + overviewInfoCurrent, + } = useProfileData(); + + + const { handleNodeSelection, setOverInfo } = useNodeSelection(graphRef, plainData, setSelectedNodeId, setOverviewInfo); - fetchMessage(); - }, []); + useGraphEvents( + graphRef, + plainData, + setOverInfo as React.Dispatch>, + setSelectedNodeId, + profileWrapRefCanvas as React.MutableRefObject, + profileWrapRef, overviewInfoCurrent, setOverviewInfo); return ( - + - -
- -
- - - {selectedNodeId !== "all" ? ( - <> - profile.id === selectedNodeId)!} - /> - stat.id === selectedNodeId)!} - /> - label.id === selectedNodeId)?.labels!} - /> - - ) : ( - profile.id === "all")!} /> - )} - + ref={profileRef} + className="bg-white w-full rounded-lg" + > + + } + handleResize={handleResize} + overviewInfoCurrent={overviewInfoCurrent} + setIsLoading={setIsLoading} + profileWrapRef={profileWrapRef} + profileWrapRefCanvas={profileWrapRefCanvas} + /> +
); } +function GraphContent({ + isLoading, + plainData, + graphSize, + graphRef, + handleResize, + overviewInfoCurrent, + setIsLoading, + profileWrapRef, + profileWrapRefCanvas, +}: { + isLoading: boolean; + plainData: Profile[]; + graphSize: IGraphSize; + graphRef: React.MutableRefObject; + handleResize: () => void; + overviewInfoCurrent: React.RefObject; + setIsLoading: React.Dispatch>; + profileWrapRef: React.RefObject; + profileWrapRefCanvas: React.RefObject; +}) { + return ( +
+ {isLoading ? ( +
loading...
+ ) : ( + { + if (isLoading) { + graph.fitView(); + graph.refresh(); + setIsLoading(false); + } + graphRef.current = graph; + graph.setMaxZoom(2); + graph.setMinZoom(0.5); + }} + /> + )} +
+ ); +} + +function SidebarContent({ + rangeData, + plainData, + selectedNodeId, + handleNodeSelection, + overviewInfo, + statisticsData, + labels, + graphSize, +}) { + return ( + +
+ + {selectedNodeId !== ALL_NODE_ID ? ( + <> + + stat.id === selectedNodeId)!} /> + label.id === selectedNodeId)?.labels!} /> + + ) : ( + + )} +
+
+ ); +} + export default ProfileGraphDashboard; diff --git a/frontend/src/components/Attributes.tsx b/frontend/src/components/Attributes.tsx index f8e49af60..471850fc3 100644 --- a/frontend/src/components/Attributes.tsx +++ b/frontend/src/components/Attributes.tsx @@ -7,6 +7,8 @@ interface AttributesProps { const Attributes: React.FC = ({ attributesData }) => { return ( + <> + {attributesData?.length > 0 && (

Attributes

@@ -14,16 +16,18 @@ const Attributes: React.FC = ({ attributesData }) => { {attributesData?.map((item, index) => (
{item.name}
-
- {item?.value?.map((value, idx) => ( -
- {value} -
- ))} +
+ {item?.value?.map((value, idx) => ( +
+ {value} +
+ ))} +
-
- ))} -
+ ))} +
+ )} + ); }; diff --git a/frontend/src/components/FlowAnalysisGraph.tsx b/frontend/src/components/FlowAnalysisGraph.tsx new file mode 100644 index 000000000..f55e1714f --- /dev/null +++ b/frontend/src/components/FlowAnalysisGraph.tsx @@ -0,0 +1,78 @@ +import { Edge, FlowAnalysisGraph, FlowAnalysisGraphConfig, IGraph } from "@ant-design/charts"; +import { debounce, isEqual } from "lodash-es"; +import { memo, useEffect, useMemo, useState } from "react"; + +import { IGraphSize, IOverview, Profile } from "../types/ProfileGraphDashboard"; + +import { mapEdgesLineWidth, getEdges, getDealData } from "../utills"; +import { useFlowAnalysisGraphConfig } from "../hooks/useFlowAnalysisGraphConfig"; + +export interface EdgeWithLineWidth extends Edge { + lineWidth: number; + _value: number; +} + +const CacheFlowAnalysisGraph = ({ + plainData, + graphSize, + graphRef, + overviewInfoCurrent, + onReady, +}: { + plainData: Profile[]; + graphSize: IGraphSize; + graphRef: React.RefObject; + overviewInfoCurrent: React.RefObject; + onReady: (graph: IGraph) => void; +}) => { + const [renderKey, setRenderKey] = useState(0); + + const handleResetView = () => { + const graph = graphRef?.current; + if (graph) { + graph.fitView(); + graph.refresh(); + } + }; + + const edgesWithLineWidth = mapEdgesLineWidth(getEdges(plainData) as EdgeWithLineWidth[]); + const data = useMemo(() => { + return { + nodes: getDealData(plainData), + edges: edgesWithLineWidth, + }; + }, [plainData, edgesWithLineWidth]); + + const config: FlowAnalysisGraphConfig = useFlowAnalysisGraphConfig({ + graphSize, + onReady, + data, + graphRef, + overviewInfoCurrent, + handleResetView, + edgesWithLineWidth, + }); + + // Debounce the rendering to reduce the number of renders + const debouncedRender = debounce(() => { + setRenderKey(prevKey => prevKey + 1); + }, 300); + + useEffect(() => { + debouncedRender(); + return () => { + debouncedRender.cancel(); + }; + }, [plainData, graphSize]); + + return ; +}; + +export default memo(CacheFlowAnalysisGraph, (pre, next) => { + const isPlainDataEqual = isEqual(pre.plainData, next.plainData); + const isGraphSizeEqual = pre.graphSize === next.graphSize; + const isGraphSizeHeightEqual = pre.graphSize.height === next.graphSize.height; + return isPlainDataEqual && isGraphSizeEqual && isGraphSizeHeightEqual; +}); + + diff --git a/frontend/src/components/MostExpensiveNodes.tsx b/frontend/src/components/MostExpensiveNodes.tsx index e39213326..fd4203653 100644 --- a/frontend/src/components/MostExpensiveNodes.tsx +++ b/frontend/src/components/MostExpensiveNodes.tsx @@ -1,33 +1,30 @@ import React from 'react'; -import { GraphData } from '../types/ProfileGraphDashboard'; - -interface Props { - data: GraphData; +import { Profile } from '../types/ProfileGraphDashboard'; +interface MostExpensiveNodesProps { + data: Profile[]; + plainData: Profile[]; selectedNodeId: string | null; handleNodeSelection: (nodeId: string) => void; } -const MostExpensiveNodes: React.FC = ({ data, selectedNodeId, handleNodeSelection }) => { - const sortedNodes = [...data.nodes].sort( - (a, b) => b.value.items[0].progress - a.value.items[0].progress - ); +const MostExpensiveNodes: React.FC = ({ data, plainData, selectedNodeId, handleNodeSelection }) => { return ( -
-
-

Most Expensive Nodes ({sortedNodes.length})

+
+
+

Most Expensive Nodes ({data?.length} of {plainData?.length})

- {sortedNodes.map((node) => ( + {data?.map((node) => (
handleNodeSelection(node.id)} + onClick={() => handleNodeSelection(node?.id!)} >
- {node.value.items[0].name} [{node.id}] + {node?.name} [{node?.id}]
- {node.value.items[0].progress}% + {node?.totalTimePercent}
))} diff --git a/frontend/src/components/ProfileOverview.tsx b/frontend/src/components/ProfileOverview.tsx index e2b8bc750..209582191 100644 --- a/frontend/src/components/ProfileOverview.tsx +++ b/frontend/src/components/ProfileOverview.tsx @@ -1,11 +1,16 @@ import React from 'react'; -import { ProfileData } from '../types/ProfileGraphDashboard'; +import { IOverview } from '../types/ProfileGraphDashboard'; +import { filterMillisecond } from '../utills'; interface ProfileOverviewProps { - profileData: ProfileData; + overviewInfo?: IOverview; + queryDuration?: number; } -const ProfileOverview: React.FC = ({ profileData }) => { +const ProfileOverview: React.FC = ({ + overviewInfo, + queryDuration = 0, +}) => { return (
@@ -16,7 +21,8 @@ const ProfileOverview: React.FC = ({ profileData }) => {
Total Execution Time
- ({profileData?.totalExecutionTime}ms) 100% + ({filterMillisecond(Math.floor(queryDuration/1000/60/24))}) + {overviewInfo?.totalTimePercent}
@@ -28,7 +34,7 @@ const ProfileOverview: React.FC = ({ profileData }) => { CPU Time
- {profileData?.cpuTimePercentage}% + {overviewInfo?.cpuTimePercent}
@@ -40,7 +46,7 @@ const ProfileOverview: React.FC = ({ profileData }) => { I/O Time
- {profileData?.ioTimePercentage}% + {overviewInfo?.waitTimePercent}
diff --git a/frontend/src/components/ProfileOverviewNode.tsx b/frontend/src/components/ProfileOverviewNode.tsx index 4c8030972..766aaccc6 100644 --- a/frontend/src/components/ProfileOverviewNode.tsx +++ b/frontend/src/components/ProfileOverviewNode.tsx @@ -1,12 +1,13 @@ import React from 'react'; -import { ProfileData } from '../types/ProfileGraphDashboard'; import { Progress } from 'antd'; +import { IOverview } from '../types/ProfileGraphDashboard'; interface ProfileOverviewNodeProps { - profileData: ProfileData; + overviewInfo?: IOverview; } -const ProfileOverviewNode: React.FC = ({ profileData }) => { +const ProfileOverviewNode: React.FC = ({ overviewInfo }) => { + const cpuTimePercent = parseFloat(overviewInfo?.cpuTimePercent || "0"); return (
@@ -14,14 +15,14 @@ const ProfileOverviewNode: React.FC = ({ profileData }
- {profileData?.cpuTimePercentage}% + {overviewInfo?.totalTimePercent}
@@ -33,7 +34,7 @@ const ProfileOverviewNode: React.FC = ({ profileData } CPU Time
- {profileData?.cpuTimePercentage}% + {overviewInfo?.cpuTimePercent}
@@ -45,7 +46,7 @@ const ProfileOverviewNode: React.FC = ({ profileData } I/O Time
- {profileData?.ioTimePercentage}% + {overviewInfo?.waitTimePercent}
diff --git a/frontend/src/components/Statistics.tsx b/frontend/src/components/Statistics.tsx index 298547b50..7ff6958ae 100644 --- a/frontend/src/components/Statistics.tsx +++ b/frontend/src/components/Statistics.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { StatisticsData } from '../types/ProfileGraphDashboard'; interface StatisticsProps { @@ -6,12 +6,18 @@ interface StatisticsProps { } const Statistics: React.FC = ({ statisticsData }) => { + const statistics = useMemo(() => statisticsData?.statistics?.slice(2), [statisticsData]); + + const showStatistics = useMemo(() => statistics?.filter((item) => Boolean(item.value)).length > 0, [statistics]); + return ( + <> + {showStatistics && (

Statistics

- {statisticsData?.statistics?.slice(2)?.map((item, index) => ( + {statistics?.map((item, index) => ( Boolean(item.value) && (
{item.name}
@@ -19,8 +25,9 @@ const Statistics: React.FC = ({ statisticsData }) => {
) ))} -
- ); -}; + )} + + ) +} export default Statistics; diff --git a/frontend/src/constants/index.ts b/frontend/src/constants/index.ts new file mode 100644 index 000000000..9c08b9d79 --- /dev/null +++ b/frontend/src/constants/index.ts @@ -0,0 +1,41 @@ +export const ALL_NODE_ID = "all"; + +export const OUTPUT_ROWS = "OutputRows"; + +export const pathMoon = [ + ["M", 19.0374 + 2, 4 + 56], + ["H", 13 + 2], + ["C", 10.7909 + 2, 4 + 56, 9 + 2, 5.79086 + 56, 9 + 2, 8 + 56], + ["C", 9 + 2, 10.2091 + 56, 10.7909 + 2, 12 + 56, 13 + 2, 12 + 56], + ["H", 19.1059 + 2], + [ + "C", + 17.8458 + 2, + 13.9437 + 56, + 15.7082 + 2, + 15.2309 + 56, + 13.2964 + 2, + 15.0062 + 56, + ], + ["C", 2.5 + 2, 14 + 56, -1 + 2, 13.5 + 56, 2 + 2, 7.5 + 56], + ["C", -2.5 + 2, 1.00616 + 56, 9.5 + 2, 0.5 + 56, 13.2964 + 2, 1.00616 + 56], + [ + "C", + 15.6724 + 2, + 1.00616 + 56, + 17.772 + 2, + 2.18999 + 56, + 19.0374 + 2, + 4 + 56, + ], + ["Z"], +]; +export const pathArrow = [ + ["M", 125.5, -18], + ["L", 131.9952, -6.75], + ["L", 119.00481, -6.75], + ["L", 125.5, -18], + ["Z"], + ["M", 125.5, -1], + ["L", 125.5, -7], +]; \ No newline at end of file diff --git a/frontend/src/less/ProfileGraphDashboard.css b/frontend/src/css/ProfileGraphDashboard.css similarity index 92% rename from frontend/src/less/ProfileGraphDashboard.css rename to frontend/src/css/ProfileGraphDashboard.css index 7afed0ace..8d217716f 100644 --- a/frontend/src/less/ProfileGraphDashboard.css +++ b/frontend/src/css/ProfileGraphDashboard.css @@ -1,10 +1,3 @@ -.App { - display: flex; - justify-content: center; - align-items: center; - height: 100vh; -} - .expensive-nodes-card { margin-top: 20px; background-color: white; @@ -92,7 +85,7 @@ .custom-statistic { display: flex; align-items: center; - margin-bottom: 8px; /* 添加一些间距 */ + margin-bottom: 8px; } .custom-dot { diff --git a/frontend/src/hooks/useFlowAnalysisGraphConfig.tsx b/frontend/src/hooks/useFlowAnalysisGraphConfig.tsx new file mode 100644 index 000000000..6c42392ae --- /dev/null +++ b/frontend/src/hooks/useFlowAnalysisGraphConfig.tsx @@ -0,0 +1,243 @@ +import { useMemo } from "react"; +import { FlowAnalysisGraphConfig } from "@ant-design/charts"; + +import { pathArrow, pathMoon } from "../constants"; +import { createRoundedRectPath, formatRows, getTextWidth } from "../utills"; +import fullScreenUrl from '../images/icons/full-screen.svg'; +import zoomInUrl from '../images/icons/zoom-in.svg'; +import zoomOutUrl from '../images/icons/zoom-out.svg'; +import downloadUrl from '../images/icons/download.svg'; +import { EdgeWithLineWidth } from "../components/FlowAnalysisGraph"; + +export const useFlowAnalysisGraphConfig = ({ + graphSize, + onReady, + data, + graphRef, + overviewInfoCurrent, + handleResetView, + edgesWithLineWidth, +}): FlowAnalysisGraphConfig => { + const getToolbarContent = ({ zoomIn, zoomOut }) => ( +
+ handleResetView()} + > + full screen + + + zoom out + + + zoom in + + + graphRef?.current?.downloadFullImage( + `databend-profile`, + "image/png" + ) + } + > + download + +
+ ); + + const getNodeStyle = edge => ({ + radius: 5, + fill: "#fff", + stroke: "#ccc", + filter: "drop-shadow(2px 3px 2px rgba(255, 255, 255, .2))", + }); + + const getNodeTitleStyle = edge => ({ + fontWeight: 600, + fill: edge?.errors?.length ? "#fff" : "#000", + }); + + const getEdgeStyle = (edge: EdgeWithLineWidth) => ({ + lineWidth: (edge?.lineWidth as number) || 1, + }); + + const getCustomNodeContent = (item, group, cfg) => { + const { startX, startY, width } = cfg; + const { text } = item; + const totalWidth = 230; + const textLength = text?.length; + const model = group?.cfg?.item?._cfg?.model; + const longRate = model?.totalTime / (overviewInfoCurrent.current?.totalTime || 1); + const isExistedError = model?.errors?.length; + const parentId = model?.parent_id; + + const textShape = group.addShape("text", { + attrs: { + textBaseline: "top", + x: startX, + y: startY, + text, + fill: isExistedError ? "rgba(255,255,255,0.8)" : "#75767a", + textAlign: "left", + }, + name: `text-${Math.random()}`, + }); + + const textWidth = textShape.getBBox().width; + if (textLength > 26 && textWidth > width) { + const ellipsisText = text.slice(0, Math.floor((width / textWidth) * textLength - 3)) + "..."; + textShape.attr("text", ellipsisText); + } + const textHeight = textShape?.getBBox().height ?? 0; + + const height = 8; + const borderRadius = 4; + const progressWidth = longRate * totalWidth; + + const backgroundPath = createRoundedRectPath( + startX, + startY + textHeight + 10, + totalWidth, + height, + borderRadius + ); + group.addShape("path", { + attrs: { + path: backgroundPath, + fill: "#f2f2f2", + }, + name: `progress-bg-${Math.random()}`, + }); + + const foregroundPath = createRoundedRectPath( + startX, + startY + textHeight + 10, + progressWidth, + height, + borderRadius + ); + group.addShape("path", { + attrs: { + path: foregroundPath, + fill: progressWidth <= 0 ? "rgba(0,0,0,0)" : "rgb(1, 117, 246)", + }, + name: `progress-fg-${Math.random()}`, + }); + + if (progressWidth > 0 && progressWidth < 9) { + group.addShape("path", { + attrs: { + path: pathMoon, + fill: isExistedError ? "#f73920" : "#fff", + }, + name: `circle-path-bg-${Math.random()}`, + }); + } + + if (parentId === "null") { + const edgeObj = edgesWithLineWidth?.find(edge => edge?.source === "null"); + group.addShape("path", { + attrs: { + path: pathArrow, + fill: "#ccc", + stroke: "#ccc", + lineWidth: edgeObj?.lineWidth || 1, + }, + name: `percentage-output-text-${Math.random()}`, + }); + const outputRowsFormat = formatRows(model?.outputRows); + group.addShape("text", { + attrs: { + textBaseline: "top", + x: 125 + getTextWidth(outputRowsFormat) / 2, + y: -30, + text: outputRowsFormat, + fill: "rgba(12, 22, 43, 0.6)", + fontWeight: "bold", + fontSize: 12, + textAlign: "right", + }, + name: "percentage-output-text", + }); + } + + const percentageText = longRate > 0 ? `${(longRate * 100).toFixed(1)}%` : "0%"; + group.addShape("text", { + attrs: { + textBaseline: "top", + x: startX + width, + y: startY - 27, + text: percentageText, + fill: isExistedError ? "#fff" : "#000", + fontSize: 11, + textAlign: "right", + }, + name: `percentage-text-${Math.random()}`, + }); + + return Math.max(textHeight, height); + }; + + return useMemo(() => ({ + ...graphSize, + onReady, + data, + layout: { + rankdir: "TB", + ranksepFunc: () => 20, + }, + toolbarCfg: { + className: 'absolute top-0 left-0 w-[100px]', + show: true, + customContent: getToolbarContent, + }, + nodeCfg: { + padding: 10, + size: [250, 40], + title: { + autoEllipsis: true, + containerStyle: { + fill: "transparent", + }, + style: getNodeTitleStyle, + }, + anchorPoints: [ + [0.5, 0], + [0.5, 1], + ], + style: getNodeStyle, + nodeStateStyles: { + highlight: { + stroke: "#2c91ff", + lineWidth: 2, + }, + hover: { + stroke: "#2c91ff", + lineWidth: 2, + }, + }, + customContent: getCustomNodeContent, + }, + edgeCfg: { + type: "cubic-vertical", + endArrow: false, + style: getEdgeStyle, + label: { + style: { + fontWeight: 600, + fill: "rgba(12, 22, 43, 0.6)", + }, + }, + startArrow: { + type: "triangle", + }, + }, + markerCfg: cfg => ({ + animate: true, + position: "bottom", + show: data.edges.filter(item => item.source === cfg.id)?.length, + }), + behaviors: ["drag-canvas", "zoom-canvas"], + }), [graphSize, onReady, data, graphRef, overviewInfoCurrent, handleResetView, edgesWithLineWidth]); +}; \ No newline at end of file diff --git a/frontend/src/hooks/useFlowGraphConfig.ts b/frontend/src/hooks/useFlowGraphConfig.ts deleted file mode 100644 index 436f946fa..000000000 --- a/frontend/src/hooks/useFlowGraphConfig.ts +++ /dev/null @@ -1,177 +0,0 @@ -import { useEffect, useRef } from "react"; -import { FlowAnalysisGraphConfig } from "@ant-design/graphs"; -import { G6 } from "@ant-design/charts"; -import { GraphData } from "../types/ProfileGraphDashboard"; - -interface UseFlowGraphConfigParams { - graphData: GraphData; - onNodeClick: (nodeId: string) => void; -} - -export const useFlowGraphConfig = ({ - graphData, - onNodeClick, -}: UseFlowGraphConfigParams): FlowAnalysisGraphConfig => { - const graphInstanceRef = useRef(null); - - useEffect(() => { - if (graphInstanceRef.current) { - const graph = graphInstanceRef.current; - - graph.on("node:click", (evt: any) => { - const clickedNode = evt.item.getModel(); - onNodeClick(clickedNode.id); - }); - - graph.on("canvas:click", () => { - onNodeClick("all"); - }); - } - }, [onNodeClick]); - - return { - data: graphData, - nodeCfg: { - size: [310, 80], - customContent: (item: any, group: any, cfg: any) => { - const { startX, startY } = cfg; - const { name, progress, title } = item; - - if (name) { - group?.addShape("text", { - attrs: { - textBaseline: "top", - x: startX + 10, - y: startY + 10, - text: name, - fill: "black", - fontWeight: "bold", - }, - name: "name", - }); - } - - if (progress !== undefined) { - group?.addShape("text", { - attrs: { - textBaseline: "top", - x: startX + 280, - y: startY + 10, - text: `${progress}%`, - fill: "black", - textAlign: "right", - }, - name: "progress", - }); - } - - if (title) { - group?.addShape("text", { - attrs: { - textBaseline: "top", - x: startX + 10, - y: startY + 30, - text: title, - fill: "gray", - }, - name: "title", - }); - } - - group?.addShape("rect", { - attrs: { - x: startX + 10, - y: startY + 60, - width: 280, - height: 10, - fill: "lightgray", - radius: [5, 5, 5, 5], - }, - name: "progress-bg", - }); - - if (progress < 5) { - group?.addShape("circle", { - attrs: { - x: startX + 10, - y: startY + 65, - r: progress, - fill: "rgb(28, 130, 242)", - }, - name: "progress-circle", - }); - } else { - group?.addShape("rect", { - attrs: { - x: startX + 10, - y: startY + 60, - width: (280 * progress) / 100, - height: 10, - fill: "rgb(28, 130, 242)", - radius: [5, 5, 5, 5], - }, - name: "progress-bar", - }); - } - - return 5; - }, - anchorPoints: [ - [0.5, 0], - [0.5, 1], - ], - items: { - containerStyle: { - fill: "#fff", - }, - padding: 0, - }, - nodeStateStyles: { - hover: { - stroke: "rgb(28, 130, 242)", - lineWidth: 2, - }, - selected: { - stroke: "rgb(28, 130, 242)", - lineWidth: 2, - }, - }, - style: { - stroke: "#cccccc", - radius: 8, - }, - title: { - containerStyle: { - fill: "transparent", - display: "flex", - justifyContent: "space-between", - alignItems: "center", - }, - style: { - fill: "#000", - fontSize: 12, - }, - }, - }, - layout: { - rankdir: "BT", - ranksepFunc: () => 12, - }, - autoFit: true, - edgeCfg: { - type: "polyline", - endArrow: { - path: G6.Arrow.triangle(10, 10, 0), - fill: "#999", - }, - style: { - stroke: "#999", - lineWidth: 1, - }, - }, - behaviors: ["drag-canvas", "zoom-canvas", "drag-node", "click-select"], - onReady: (graph) => { - graphInstanceRef.current = graph; - }, - }; -}; diff --git a/frontend/src/hooks/useGraphEvents.ts b/frontend/src/hooks/useGraphEvents.ts new file mode 100644 index 000000000..33df08e90 --- /dev/null +++ b/frontend/src/hooks/useGraphEvents.ts @@ -0,0 +1,99 @@ +import { useCallback, useEffect, useState } from "react"; +import { ALL_NODE_ID } from "../constants"; +import { useRefValueListener } from "./useRefValueListener"; +import { IGraph } from "@ant-design/charts"; +import { IOverview, Profile } from "../types/ProfileGraphDashboard"; + +export function useGraphEvents( + graphRef: React.RefObject, + plainData: Profile[], + setOverInfo: React.Dispatch>, + setSelectedNodeId: React.Dispatch>, + profileWrapRefCanvas: React.MutableRefObject, + profileWrapRef: React.RefObject, + overviewInfoCurrent: React.RefObject, + setOverviewInfo: React.Dispatch>, +) { + + const [graph, setGraph] = useState(graphRef.current); + + useRefValueListener(graphRef, (graph: IGraph) => { + setGraph(graph); + }); + + const getAllNodes = useCallback(() => { + return graph?.getNodes(); + }, [graph]); + + const setNodeActive = useCallback(( node) => { + graph?.setItemState(node, "highlight", true); + }, [graph]); + + const clearNodeActive = useCallback(() => { + getAllNodes()?.forEach(n => { + graph?.clearItemStates(n); + }); + }, [graph, getAllNodes]); + + useEffect(() => { + if (!graph) return; + + const handleNodeClick = (evt) => { + const modal = evt.item._cfg.model; + setOverInfo({ + ...plainData.find(item => item.id === modal.id), + } as IOverview); + setSelectedNodeId(modal.id); + + const nodes = getAllNodes(); + const id = evt.item._cfg.id; + const node = nodes?.find(node => node?._cfg?.id === id); + nodes + ?.filter(node => node?._cfg?.id !== id) + .forEach(n => { + graph?.clearItemStates(n); + }); + + setNodeActive(node); + }; + + const handleNodeMouseLeave = () => { + if (!profileWrapRefCanvas.current) { + profileWrapRefCanvas.current = document.getElementsByTagName("canvas")[0]; + } + profileWrapRefCanvas.current.style.cursor = "move"; + }; + + const handleCanvasClick = () => { + setSelectedNodeId(ALL_NODE_ID); + setOverviewInfo(overviewInfoCurrent.current || undefined); + clearNodeActive(); + }; + + const handleCanvasDragStart = () => { + if (profileWrapRef?.current) { + profileWrapRef.current.style.userSelect = "none"; + } + }; + + const handleCanvasDragEnd = () => { + if (profileWrapRef?.current) { + profileWrapRef.current.style.userSelect = "unset"; + } + }; + + graph.on("node:click", handleNodeClick); + graph.on("node:mouseleave", handleNodeMouseLeave); + graph.on("canvas:click", handleCanvasClick); + graph.on("canvas:dragstart", handleCanvasDragStart); + graph.on("canvas:dragend", handleCanvasDragEnd); + + return () => { + graph.off("node:click", handleNodeClick); + graph.off("node:mouseleave", handleNodeMouseLeave); + graph.off("canvas:click", handleCanvasClick); + graph.off("canvas:dragstart", handleCanvasDragStart); + graph.off("canvas:dragend", handleCanvasDragEnd); + }; + }, [graph, plainData, setOverInfo, setSelectedNodeId, getAllNodes, setNodeActive, clearNodeActive, profileWrapRefCanvas, profileWrapRef, overviewInfoCurrent, setOverviewInfo]); +} \ No newline at end of file diff --git a/frontend/src/hooks/useGraphSize.ts b/frontend/src/hooks/useGraphSize.ts new file mode 100644 index 000000000..bc5ae251c --- /dev/null +++ b/frontend/src/hooks/useGraphSize.ts @@ -0,0 +1,34 @@ +import { useState, useEffect, useRef } from "react"; +import { useReshape } from "./useReshape"; +import { IGraphSize } from "../types/ProfileGraphDashboard"; + + +export function useGraphSize() { + const [graphSize, setGraphSize] = useState({ + width: 0, + height: window.innerHeight / 2, + }); + + + const profileRef = useRef(null); + + const { reshapeDOM } = useReshape(); + + const handleResize = () => { + if (profileRef?.current) { + setGraphSize({ + width: profileRef.current.offsetWidth - 408, + height: window.innerHeight, + }); + } + }; + + useEffect(() => { + handleResize(); + reshapeDOM(() => { + handleResize(); + }); + }, []); + + return { graphSize, profileRef, handleResize }; +} diff --git a/frontend/src/hooks/useNodeSelection.ts b/frontend/src/hooks/useNodeSelection.ts new file mode 100644 index 000000000..0583caa59 --- /dev/null +++ b/frontend/src/hooks/useNodeSelection.ts @@ -0,0 +1,66 @@ +import { useCallback } from "react"; +import { calculateNodeOffsets, setNodeActiveState } from "../utills"; +import { IGraph } from "@ant-design/charts"; +import { IErrors, IOverview, IStatisticsDesc, Profile } from "../types/ProfileGraphDashboard"; + +export function useNodeSelection( + graphRef: React.RefObject, + plainData: Profile[], + setSelectedNodeId: React.Dispatch>, + setOverviewInfo: React.Dispatch>, +): { + handleNodeSelection: (nodeId: string) => void; + setOverInfo: (data: IOverview) => void; +} { + const centerNodeInView = useCallback((nodeId) => { + if (!graphRef.current) return; + const graph: IGraph = graphRef.current; + const nodes = graph?.getNodes(); + const node = nodes?.find(n => n?._cfg?.id === nodeId); + if (node) { + const { offsetX, offsetY } = calculateNodeOffsets(graph, node); + setNodeActiveState(graph, node, true); + graph?.moveTo(offsetX, offsetY); + } + }, [graphRef]); + + const setOverInfo = useCallback((data: IOverview) => { + const { + totalTime, + totalTimePercent, + cpuTime, + waitTime, + cpuTimePercent, + waitTimePercent, + labels, + statisticsDescArray, + errors, + name, + } = data; + setOverviewInfo({ + cpuTime, + waitTime, + totalTime, + totalTimePercent, + cpuTimePercent, + waitTimePercent, + labels, + statisticsDescArray: statisticsDescArray as IStatisticsDesc[], + errors: errors as unknown as IErrors[], + name, + }); + }, [setOverviewInfo]); + + const handleNodeSelection = useCallback((nodeId: string) => { + + const selectedItem = plainData?.find(item => item.id === nodeId); + setSelectedNodeId(nodeId); + + if (selectedItem) { + setOverInfo(selectedItem as unknown as IOverview); + centerNodeInView(selectedItem.id); + } + }, [plainData, setSelectedNodeId, setOverInfo, centerNodeInView]); + + return { handleNodeSelection, setOverInfo }; +} diff --git a/frontend/src/hooks/useProfileData.ts b/frontend/src/hooks/useProfileData.ts new file mode 100644 index 000000000..1c31596c9 --- /dev/null +++ b/frontend/src/hooks/useProfileData.ts @@ -0,0 +1,160 @@ +import { useState, useEffect, useRef } from "react"; +import { clone } from "lodash-es"; +import { transformErrors, getPercent } from "../utills"; +import { Profile, StatisticsDesc, StatisticsData, AttributeData, IOverview, MessageResponse } from "../types/ProfileGraphDashboard"; + +const CPU_TIEM_KEY = "CpuTime"; +const WAIT_TIEM_KEY = "WaitTime"; + +export function useProfileData(): { + plainData: Profile[]; + rangeData: Profile[]; + statisticsData: StatisticsData[]; + labels: AttributeData[]; + overviewInfo: IOverview | undefined; + setOverviewInfo: React.Dispatch>; + isLoading: boolean; + setIsLoading: React.Dispatch>; + overviewInfoCurrent: React.RefObject; +} { + const [plainData, setPlainData] = useState([]); + const [rangeData, setRangeData] = useState([]); + const [statisticsData, setStatisticsData] = useState([]); + const [labels, setLabels] = useState([]); + const [overviewInfo, setOverviewInfo] = useState(undefined); + const [isLoading, setIsLoading] = useState(true); + const overviewInfoCurrent = useRef(undefined); + + useEffect(() => { + const fetchMessage = async () => { + try { + const response: Response = await fetch("/api/message"); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const result: MessageResponse = await response.json(); + + const data = JSON.parse(result?.result); + + const profiles = transformProfiles(data.profiles, data.statistics_desc); + const overviewInfo = calculateOverviewInfo(profiles, data.statistics_desc); + + setPlainData(profiles); + setRangeData(getRangeData(profiles)); + setOverviewInfo(overviewInfo); + overviewInfoCurrent.current = overviewInfo; + + setStatisticsData(getStatisticsData(data.profiles, data.statistics_desc) as StatisticsData[]); + setLabels(getLabels(data.profiles) as AttributeData[]); + } catch (error) { + console.error("Error fetching message:", error); + } finally { + setIsLoading(false); + } + }; + + setIsLoading(true); + fetchMessage(); + }, []); + + function transformProfiles(profiles: Profile[], statistics_desc: StatisticsDesc) { + + const cpuTimeIndex = statistics_desc[CPU_TIEM_KEY]?.index; + const waitTimeIndex = statistics_desc[WAIT_TIEM_KEY]?.index; + let cpuTime = 0; + let waitTime = 0; + + profiles.forEach(item => { + item.id = String(item.id); + item.parent_id = String(item.parent_id); + const cpuT = item?.statistics[cpuTimeIndex] || 0; + const waitT = item?.statistics[waitTimeIndex] || 0; + item.totalTime = cpuT + waitT; + item.cpuTime = cpuT; + item.waitTime = waitT; + cpuTime += cpuT; + waitTime += waitT; + item.errors = item?.errors?.length > 0 ? transformErrors(item?.errors) : []; + item.statisticsDescArray = createStatisticsDescArray(item, statistics_desc); + }); + + const totalTime = cpuTime + waitTime; + profiles.forEach(item => { + item.totalTimePercent = getPercent(item?.totalTime, totalTime); + item.cpuTimePercent = getPercent(item?.cpuTime, item.totalTime); + item.waitTimePercent = getPercent(item?.waitTime, item.totalTime); + }); + + return profiles; + } + + function createStatisticsDescArray(item: Profile, statistics_desc: StatisticsDesc) { + return Object.entries(statistics_desc).map( + ([_type, descObj]) => ({ + _type, + desc: descObj?.desc, + display_name: descObj?.display_name || descObj?.displayName, + index: descObj?.index, + unit: descObj.unit, + plain_statistics: descObj?.plain_statistics, + _value: item.statistics[descObj?.index], + }) + ); +} + + function calculateOverviewInfo(profiles: Profile[], statistics_desc: StatisticsDesc) { + const cpuTime = profiles.reduce((sum: number, item: Profile) => sum + item.cpuTime, 0); + const waitTime = profiles.reduce((sum: number, item: Profile) => sum + item.waitTime, 0); + const totalTime = cpuTime + waitTime; + const cpuTimePercent = getPercent(cpuTime, totalTime); + const waitTimePercent = getPercent(waitTime, totalTime); + + return { + cpuTime, + waitTime, + totalTime, + totalTimePercent: "100%", + cpuTimePercent, + waitTimePercent, + statisticsDescArray: [], + errors: [], + }; + } + + function getRangeData(profiles: Profile[]) { + return clone(profiles) + ?.filter(item => parseFloat(item.totalTimePercent) > 0) + ?.sort((a, b) => b.totalTime - a.totalTime); + } + + function getStatisticsData(profiles: Profile[], statistics_desc: StatisticsDesc) { + return profiles.map(profile => { + const statistics = Object.entries(statistics_desc).map(([key, value]) => ({ + name: value.display_name || key, + desc: value.desc, + value: profile.statistics[value.index], + unit: value.unit, + })); + return { statistics, id: profile?.id?.toString() }; + }); + } + + function getLabels(profiles: Profile[]) { + return profiles.map(profile => ({ + labels: profile.labels, + id: profile?.id?.toString(), + })); + } + + return { + plainData, + rangeData, + statisticsData, + labels, + overviewInfo, + setOverviewInfo, + isLoading, + setIsLoading, + overviewInfoCurrent, + }; +} \ No newline at end of file diff --git a/frontend/src/hooks/useRefValueListener.ts b/frontend/src/hooks/useRefValueListener.ts new file mode 100644 index 000000000..0a75eba1e --- /dev/null +++ b/frontend/src/hooks/useRefValueListener.ts @@ -0,0 +1,16 @@ +import { useEffect, useState } from "react"; + +// Custom hook to detect changes in ref value +export function useRefValueListener( + ref: React.RefObject, + callback: (value: any) => void +) { + const [currentValue, setCurrentValue] = useState(ref.current); + + useEffect(() => { + if (currentValue !== ref.current) { + callback(ref.current); + setCurrentValue(ref.current); + } + }, [ref, currentValue, callback]); +} \ No newline at end of file diff --git a/frontend/src/hooks/useReshape.ts b/frontend/src/hooks/useReshape.ts new file mode 100644 index 000000000..c97273691 --- /dev/null +++ b/frontend/src/hooks/useReshape.ts @@ -0,0 +1,40 @@ +import { useEffect, useRef } from 'react'; + +/** + * Custom hook to handle DOM reshaping events and register callbacks. + * + * @returns {Object} - An object containing the reshapeDOM function. + */ +export const useReshape = () => { + const callbackRef = useRef<(() => void) | null>(null); + + /** + * Registers a callback to be called when a DOM reshape event occurs. + * @param {Function} callback - The function to be called on DOM reshape. + */ + const reshapeDOM = (callback: () => void) => { + // Store the callback in a ref to keep a stable reference + callbackRef.current = callback; + }; + + useEffect(() => { + // Handler to call the stored callback on window resize + const handleResize = () => { + if (callbackRef.current) { + callbackRef.current(); + } + }; + + // Attach resize listener + window.addEventListener('resize', handleResize); + + // Cleanup listener on unmount + return () => { + window.removeEventListener('resize', handleResize); + }; + }, []); + + return { + reshapeDOM, + }; +}; diff --git a/frontend/src/images/icons/download.svg b/frontend/src/images/icons/download.svg new file mode 100644 index 000000000..0a181f352 --- /dev/null +++ b/frontend/src/images/icons/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/icons/full-screen.svg b/frontend/src/images/icons/full-screen.svg new file mode 100644 index 000000000..540d3b8ec --- /dev/null +++ b/frontend/src/images/icons/full-screen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/icons/zoom-in.svg b/frontend/src/images/icons/zoom-in.svg new file mode 100644 index 000000000..2de7d680f --- /dev/null +++ b/frontend/src/images/icons/zoom-in.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/images/icons/zoom-out.svg b/frontend/src/images/icons/zoom-out.svg new file mode 100644 index 000000000..1901bfc15 --- /dev/null +++ b/frontend/src/images/icons/zoom-out.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css index ec2585e8c..a90f0749c 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,13 +1,4 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} +@tailwind base; +@tailwind components; +@tailwind utilities; -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index c7428bfff..da6efb191 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; + import ProfileGraphDashboard from './ProfileGraphDashboard'; const root = ReactDOM.createRoot( diff --git a/frontend/src/types/ProfileGraphDashboard.ts b/frontend/src/types/ProfileGraphDashboard.ts index f56c69518..5b140912a 100644 --- a/frontend/src/types/ProfileGraphDashboard.ts +++ b/frontend/src/types/ProfileGraphDashboard.ts @@ -13,6 +13,8 @@ export interface Node { } export interface Edge { + lineWidth: any; + _value: undefined; source: string; target: string; } @@ -70,15 +72,62 @@ export interface Metric { } export interface Profile { - id: number; + waitTimePercent: string; + cpuTimePercent: string; + totalTimePercent: string; + statisticsDescArray: { _type: string; desc: string; display_name: string | undefined; index: number; unit: ("NanoSeconds" | "Bytes" | "Rows" | "Count" | "MillisSeconds") | undefined; plain_statistics: boolean | undefined; _value: number; }[]; + waitTime: number; + cpuTime: number; + totalTime: number; + id?: string; name: string; - parent_id: number | null; + parent_id: number | string | null; title: string; labels: Label[]; statistics: number[]; metrics?: Record; + errors: string[]; } export interface MessageResponse { result: string; -} \ No newline at end of file +} + +export type TUnit = "NanoSeconds" | "MillisSeconds" | "Bytes" | "Rows" | "Count"; +export interface IStatisticsDesc { + _type: string; + desc: string; + index: number; + _value: any; + display_name: string; + displayName?: string; + plain_statistics?: boolean; + unit?: TUnit; +} + +export interface IErrors { + backtrace: string; + detail: string; + message: string; + _errorType: string; +} + +export interface IOverview { + cpuTime: number; + waitTime: number; + totalTime: number; + isTotalBigerZero?: boolean; + totalTimePercent?: string; + cpuTimePercent?: string; + waitTimePercent?: string; + id?: string; + labels?: { name: string; value: any[] }[]; + statisticsDescArray?: IStatisticsDesc[]; + errors?: IErrors[]; + name?: string; +} + +export interface IGraphSize { + width: number; + height: number; +} diff --git a/frontend/src/utills/graph.ts b/frontend/src/utills/graph.ts new file mode 100644 index 000000000..f4655326a --- /dev/null +++ b/frontend/src/utills/graph.ts @@ -0,0 +1,197 @@ +import { IGraph, INode } from "@ant-design/charts"; +import { EdgeWithLineWidth } from "../components/FlowAnalysisGraph"; +import { OUTPUT_ROWS } from "../constants"; +import { Profile } from "../types/ProfileGraphDashboard"; + +/** + * calculate node offsets + * @param graph + * @param node + * @returns + */ +export function calculateNodeOffsets(graph: IGraph, node: INode) { + const zoom: number = graph?.getZoom() || 1; + const x: number = node?._cfg?.bboxCache?.x || 0; + const y: number = node?._cfg?.bboxCache?.y || 0; + const width: number = graph?.getWidth() || 0; + const height: number = graph?.getHeight() || 0; + return { + offsetX: width / 2 - x * zoom, + offsetY: height / 2 - y * zoom + 20, + }; +} + +/** + * set node active state + * @param graph + * @param node + * @param clear + */ +export function setNodeActiveState(graph: IGraph, node: INode, clear = false) { + if (clear) { + graph.getNodes().forEach(n => graph.clearItemStates(n)); + } + graph.setItemState(node as any, "highlight", true); +} + +/** + * format rows + * @param value + * @returns + */ +export function formatRows(value: number): string { + if (value < 1000) return value.toString(); + if (value >= 1000 && value < 1000000) return (value / 1000).toFixed(1) + "K"; + if (value >= 1000000 && value < 1000000000) return (value / 1000000).toFixed(1) + "M"; + return (value / 1000000000).toFixed(1) + "B"; +} + +/** + * get text width + * @param text + * @param font + * @returns + */ +export function getTextWidth(text: string, font: string = '12px Arial'): number { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + if (!context) return 0; + context.font = font; + const metrics = context.measureText(text); + return metrics.width; +} + +/** + * map edges line width + * @param edges + * @returns + */ +export function mapEdgesLineWidth(edges: EdgeWithLineWidth[]) { + if (!edges || edges.length <= 2) return edges; + + // Define the minimum and maximum lineWidth + const minLineWidth = 1; + const maxLineWidth = 4.5; + + // Extract values and calculate min and max in a single pass + let minValue = Infinity; + let maxValue = -Infinity; + const values: number[] = []; + + edges.forEach((edge: EdgeWithLineWidth) => { + if (edge?._value !== undefined) { + values.push(edge?._value); + if (edge?._value < minValue) minValue = edge?._value; + if (edge?._value > maxValue) maxValue = edge?._value; + } + }); + + if (values.length === 0) return edges; + + const range = maxValue - minValue; + const scale = range === 0 ? 0 : (maxLineWidth - minLineWidth) / range; + + edges.forEach(edge => { + if (edge._value !== undefined) { + edge.lineWidth = minLineWidth + (edge._value - minValue) * scale; + } else { + edge.lineWidth = minLineWidth; + } + }); + + return edges; +} + +/** + * create rounded rect path + * @param x + * @param y + * @param width + * @param height + * @param radius + * @returns + */ +export const createRoundedRectPath = (x: number, y: number, width: number, height: number, radius: number) => [ + ["M", x + radius, y], + ["l", width - radius * 2, 0], + ["a", radius, radius, 0, 0, 1, radius, radius], + ["l", 0, height - radius * 2], + ["a", radius, radius, 0, 0, 1, -radius, radius], + ["l", -width + radius * 2, 0], + ["a", radius, radius, 0, 0, 1, -radius, -radius], + ["l", 0, -height + radius * 2], + ["a", radius, radius, 0, 0, 1, radius, -radius], + ["Z"], +]; + +/** + * get deal data for graph + * @param plainData + * @returns + */ +export const getDealData = (plainData: Profile[]) => { + if (!plainData || plainData.length === 0) return []; + + let outputRowsIndex = -1; + + // Find the index of OUTPUT_ROWS once + for (const node of plainData) { + const { statisticsDescArray } = node; + if (statisticsDescArray && outputRowsIndex === -1) { + outputRowsIndex = statisticsDescArray.findIndex(item => item._type === OUTPUT_ROWS); + if (outputRowsIndex !== -1) break; + } + } + + // If OUTPUT_ROWS index is not found, set it to 0 + if (outputRowsIndex === -1) outputRowsIndex = 0; + + return plainData.map(node => { + const { title, name, id, statisticsDescArray } = node; + const outputRows = statisticsDescArray?.[outputRowsIndex]?._value || 0; + + return { + totalTime: node.totalTime, + id: id, + outputRows, + value: { + title: (name?.length >= 26 ? name.slice(0, 26) + "..." : name || " ") + ` [${id}]`, + items: [{ text: title || " " }], + }, + }; + }); +}; + + +/** + * get edges for graph + * @param plainData + * @returns + */ +export const getEdges = (plainData: Profile[]): EdgeWithLineWidth[] => { + if (!plainData || plainData.length === 0) return []; + + let outputRowsIndex = -1; + + // Find the index of OUTPUT_ROWS once + for (const node of plainData) { + const { statisticsDescArray } = node; + if (statisticsDescArray && outputRowsIndex === -1) { + outputRowsIndex = statisticsDescArray.findIndex(item => item._type === OUTPUT_ROWS); + if (outputRowsIndex !== -1) break; + } + } + + // If OUTPUT_ROWS index is not found, set it to 0 + if (outputRowsIndex === -1) outputRowsIndex = 0; + + return plainData.map(node => { + const { statisticsDescArray, parent_id, id } = node; + const outputRows = statisticsDescArray?.[outputRowsIndex]?._value || 0; + const nodeInfo = { source: parent_id, target: id }; + + return outputRows <= 0 + ? nodeInfo + : { ...nodeInfo, value: formatRows(outputRows), _value: outputRows }; + }) as unknown as EdgeWithLineWidth[]; +}; diff --git a/frontend/src/utills/index.ts b/frontend/src/utills/index.ts new file mode 100644 index 000000000..640b3c93f --- /dev/null +++ b/frontend/src/utills/index.ts @@ -0,0 +1,58 @@ +export * from "./graph"; + +/** + * Formats a given percentage value. + * @param {number} numerator - The numerator of the percentage. + * @param {number} denominator - The denominator of the percentage. + * @returns {string} - The formatted percentage string. + */ +export function getPercent(numerator: number, denominator: number): string { + if (denominator === 0) { + return "0%"; + } + const percent = (numerator / denominator) * 100; + return `${percent.toFixed(1)}%`; +} + +/** + * Formats a given millisecond value into a human-readable time format. + * @param {number} milliseconds - The value in milliseconds to be formatted. + * @returns {string} - The formatted time string. + */ +export function filterMillisecond(milliseconds: number): string { + if (typeof milliseconds !== 'number' || isNaN(milliseconds) || milliseconds < 0) { + return "Invalid Input"; // Return error message for invalid input + } + + if (milliseconds < 1000) { + // Less than 1 second, show in milliseconds + return `${milliseconds} ms`; + } else if (milliseconds < 60000) { + // Less than 1 minute, show in seconds (up to two decimal places) + const seconds = (milliseconds / 1000).toFixed(2); + return `${seconds} s`; + } else if (milliseconds < 3600000) { + // Less than 1 hour, show in minutes and seconds + const minutes = Math.floor(milliseconds / 60000); + const seconds = ((milliseconds % 60000) / 1000).toFixed(2); + return `${minutes} min ${seconds} s`; + } else { + // More than 1 hour, show in hours, minutes, and seconds + const hours = Math.floor(milliseconds / 3600000); + const minutes = Math.floor((milliseconds % 3600000) / 60000); + const seconds = ((milliseconds % 60000) / 1000).toFixed(2); + return `${hours} h ${minutes} min ${seconds} s`; + } +} + +/** + * Transforms the errors array by extracting the error type and merging it with the error details. + * @param {any[]} errors - The array of errors to be transformed. + * @returns {any[]} - The transformed array of errors. + */ +export function transformErrors(errors) { + return errors.map(error => { + const type = Object.keys(error)[0]; + return { _errorType: type, ...error[type] }; + }); +} \ No newline at end of file diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 000000000..bc7b01a1f --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'], + // darkMode: false, // or 'media' or 'class' + content: [], + theme: { + extend: {}, + }, + plugins: [], +} + diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index a273b0cfc..2093077dc 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -6,6 +6,7 @@ "dom.iterable", "esnext" ], + "noImplicitAny": false, "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, diff --git a/frontend/yarn.lock b/frontend/yarn.lock index cc96ca74c..e2323441b 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -472,29 +472,29 @@ integrity sha512-hhJOMThec51nU4Fe5p/viLlNIL71uDEgYFzKPajWjr2715SFG1HAgiP6AVylIeqBcAZ04u3Lw7usjl/TuI5RuQ== "@antv/hierarchy@^0.6.10": - version "0.6.12" - resolved "https://registry.yarnpkg.com/@antv/hierarchy/-/hierarchy-0.6.12.tgz#f35e081acd41ed7bb870322e5ac9038e5f68459d" - integrity sha512-WvWT9WYtm2SvYunm1HtzrHazvOozeP4cPFDhJWsnLzmTGMX/tNhsoCD3O+DDB3aeDY8fyM+wfZDvLv7+/4lIeA== - -"@antv/l7-component@2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@antv/l7-component/-/l7-component-2.22.0.tgz#9124f7be241f49b72aab13a60deb6cc3d05443b2" - integrity sha512-xJsgxg707A65dsBGucDqDKPyYNF0kHPLqWeNTpqKdOxboOw5zhUXdVzOVj9QN/e3ITTTpCvTeQJc6rmche7Q7Q== - dependencies: - "@antv/l7-core" "2.22.0" - "@antv/l7-layers" "2.22.0" - "@antv/l7-utils" "2.22.0" + version "0.6.14" + resolved "https://registry.yarnpkg.com/@antv/hierarchy/-/hierarchy-0.6.14.tgz#4e8b4966c9c2a44aaa6f9da7008c4bd44d490385" + integrity sha512-V3uknf7bhynOqQDw2sg+9r9DwZ9pc6k/EcqyTFdfXB1+ydr7urisP0MipIuimucvQKN+Qkd+d6w601r1UIroqQ== + +"@antv/l7-component@2.22.1": + version "2.22.1" + resolved "https://registry.yarnpkg.com/@antv/l7-component/-/l7-component-2.22.1.tgz#e59459ba348a5dc24fbb1430eab88ce20c4d59a5" + integrity sha512-Sf2TIch4QV/5yhlNV3ktNSjrfCnbuNTLzOifG98ybVkZZJTvQKvTV46AmOcf9DE4hrod1F4VcBTwdVJGXKGryw== + dependencies: + "@antv/l7-core" "2.22.1" + "@antv/l7-layers" "2.22.1" + "@antv/l7-utils" "2.22.1" "@babel/runtime" "^7.7.7" eventemitter3 "^4.0.0" supercluster "^7.0.0" -"@antv/l7-core@2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@antv/l7-core/-/l7-core-2.22.0.tgz#291f8cbb0f60192d6050e5e57b472ffaf2b03dbf" - integrity sha512-hzFBIWHsYJqYzGPFebynybHh70Qc6eG2KBUCTLS4ZlQPUkIF0NUSADYb4vt1qqjH4H3U2mGbs8ICdf7b12omuw== +"@antv/l7-core@2.22.1": + version "2.22.1" + resolved "https://registry.yarnpkg.com/@antv/l7-core/-/l7-core-2.22.1.tgz#fc85c3c15652c2f1bbcc5610eed8c4878ea54d8a" + integrity sha512-V0E8StMJYsw1269XpfUizbVLCwRZfyj/sb5KIJQ1vyyCTpSaHSdHAAFDBjd/nOXd/CP6xxxO1u5tUxyqWexQvg== dependencies: "@antv/async-hook" "^2.2.9" - "@antv/l7-utils" "2.22.0" + "@antv/l7-utils" "2.22.1" "@babel/runtime" "^7.7.7" "@mapbox/tiny-sdf" "^1.2.5" "@turf/helpers" "^6.1.4" @@ -505,16 +505,16 @@ hammerjs "^2.0.8" viewport-mercator-project "^6.2.1" -"@antv/l7-layers@2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@antv/l7-layers/-/l7-layers-2.22.0.tgz#f71ea23f0a060aa963884afc26f0ebcffba4d8e0" - integrity sha512-QdkvzLKrsJOTT8uIBQzZ77pN9/B4AbvXWRcP/gsL7QXd81HQp0mUDDKUzi+SkmM6qUm80V/Z6/OE1wo1ScLduA== +"@antv/l7-layers@2.22.1": + version "2.22.1" + resolved "https://registry.yarnpkg.com/@antv/l7-layers/-/l7-layers-2.22.1.tgz#28a419a3baceb2b55ef2072cf82fdfea837fb593" + integrity sha512-rBsx3+Xi3R2Ak/vIXNwOIePSQK1IAcwvyHFddVFHdtCfSchmcC1tdtpPmg83VKoIHmtmC+dEQb1l1/fWSK5usg== dependencies: "@antv/async-hook" "^2.2.9" - "@antv/l7-core" "2.22.0" - "@antv/l7-maps" "2.22.0" - "@antv/l7-source" "2.22.0" - "@antv/l7-utils" "2.22.0" + "@antv/l7-core" "2.22.1" + "@antv/l7-maps" "2.22.1" + "@antv/l7-source" "2.22.1" + "@antv/l7-utils" "2.22.1" "@babel/runtime" "^7.7.7" "@mapbox/martini" "^0.2.0" "@turf/clone" "^6.5.0" @@ -533,27 +533,27 @@ gl-vec2 "^1.3.0" polyline-miter-util "^1.0.1" -"@antv/l7-map@2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@antv/l7-map/-/l7-map-2.22.0.tgz#3cfffd70ddbb8c395c348498493944b0f773b2e7" - integrity sha512-RqBmm4nLqGcSgf7PaqUIcd0BwRqw9G5sk/OAVfJJHwPg2QLyzGTMrYdkuH5PbHjWLjKZ4P5AuQ4jTq3bE/NUJA== +"@antv/l7-map@2.22.1": + version "2.22.1" + resolved "https://registry.yarnpkg.com/@antv/l7-map/-/l7-map-2.22.1.tgz#bcb2dfbd38643db0c97255d5bd3cfedfa797e615" + integrity sha512-zVAxbiuYfOdeXOzc2jXWJm7imCbC12SB3/E6fiM8u+k+7FW4x2ijIDSG/UaHvuylKa2SK/uXW2LaOsj7FVImNQ== dependencies: - "@antv/l7-utils" "2.22.0" + "@antv/l7-utils" "2.22.1" "@babel/runtime" "^7.7.7" "@mapbox/point-geometry" "^0.1.0" "@mapbox/unitbezier" "^0.0.1" eventemitter3 "^4.0.4" gl-matrix "^3.1.0" -"@antv/l7-maps@2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@antv/l7-maps/-/l7-maps-2.22.0.tgz#b27c8d74a6eee577c42edafb3848c2c3175bdc7c" - integrity sha512-FHuZjn+adGpVb9sRSyMdD5iByoBed8y/p99v/r7Pct8RRGgG0gtFtAT6lVPTlp335cD+AnjGInFNUXHKzv8t0A== +"@antv/l7-maps@2.22.1": + version "2.22.1" + resolved "https://registry.yarnpkg.com/@antv/l7-maps/-/l7-maps-2.22.1.tgz#a4c522759490517eb8de8f55a5f307529ecf03d8" + integrity sha512-6cYqNMSM07hQ0X+Vm8gUmy7RXpD0ZbZAq4fGJ7jmI1rSwMubvWntEZj6AX2856Gcas7HUh7I4bPeqxkRo5gu6Q== dependencies: "@amap/amap-jsapi-loader" "^1.0.1" - "@antv/l7-core" "2.22.0" - "@antv/l7-map" "2.22.0" - "@antv/l7-utils" "2.22.0" + "@antv/l7-core" "2.22.1" + "@antv/l7-map" "2.22.1" + "@antv/l7-utils" "2.22.1" "@babel/runtime" "^7.7.7" eventemitter3 "^4.0.0" gl-matrix "^3.1.0" @@ -561,39 +561,39 @@ maplibre-gl "^3.5.2" viewport-mercator-project "^6.2.1" -"@antv/l7-renderer@2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@antv/l7-renderer/-/l7-renderer-2.22.0.tgz#c7134e639f454a40cbf3990b4250939e16a21357" - integrity sha512-tFwqO9ElVe4X+k9NdOzzDBZ8oLIEFxEjHaIMCV2R10KLjQh0B4XBFeJ8OUBKA+ndKmhflKE4oVJAu1l4I9cG0A== +"@antv/l7-renderer@2.22.1": + version "2.22.1" + resolved "https://registry.yarnpkg.com/@antv/l7-renderer/-/l7-renderer-2.22.1.tgz#94a86d35cec051c23c06ed3e90d6a3dbd0f80d65" + integrity sha512-mnuCDgeocs8AX+YUoOF9hfDDkX0GwbfFKc87Y46eXWC47pIY+7HpqULKOZNWqOoTVVcOiueS/6l2/k6AnBdMPA== dependencies: "@antv/g-device-api" "^1.6.4" - "@antv/l7-core" "2.22.0" - "@antv/l7-utils" "2.22.0" + "@antv/l7-core" "2.22.1" + "@antv/l7-utils" "2.22.1" "@babel/runtime" "^7.7.7" regl "1.6.1" -"@antv/l7-scene@2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@antv/l7-scene/-/l7-scene-2.22.0.tgz#9c14e9f8d8fe32047ba56db31a3fc209a38ef31a" - integrity sha512-aW6iRi7CvZ8ctSLSUkFoF+qFKQb/7vUjWYm6/zQ2KYs5KhASEg/bnLaPoPL+D+a7ON3FeUYDhP8NW/MH3J/T2Q== - dependencies: - "@antv/l7-component" "2.22.0" - "@antv/l7-core" "2.22.0" - "@antv/l7-layers" "2.22.0" - "@antv/l7-maps" "2.22.0" - "@antv/l7-renderer" "2.22.0" - "@antv/l7-utils" "2.22.0" +"@antv/l7-scene@2.22.1": + version "2.22.1" + resolved "https://registry.yarnpkg.com/@antv/l7-scene/-/l7-scene-2.22.1.tgz#98a2123ca1b6d0f0b824e2895161070123411129" + integrity sha512-7AyGBhFSOwv8heoYaDqOr47+KftZQvLfSYD+p+pd2y7Y5B85+oNGXbPV/zCjfrSt15R1fzxt9v7GgruQjx/+Iw== + dependencies: + "@antv/l7-component" "2.22.1" + "@antv/l7-core" "2.22.1" + "@antv/l7-layers" "2.22.1" + "@antv/l7-maps" "2.22.1" + "@antv/l7-renderer" "2.22.1" + "@antv/l7-utils" "2.22.1" "@babel/runtime" "^7.7.7" eventemitter3 "^4.0.7" -"@antv/l7-source@2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@antv/l7-source/-/l7-source-2.22.0.tgz#60db2546785dae250fb2a677fc4f0f28ff0c91a4" - integrity sha512-e+Kq5IRI4TkPVJANkKnSZlELqLHQw+j9XxasnWbHnvGaWYFPweByLtCisUxnN+QXOjezwa9DedyNHEgfgOmKbQ== +"@antv/l7-source@2.22.1": + version "2.22.1" + resolved "https://registry.yarnpkg.com/@antv/l7-source/-/l7-source-2.22.1.tgz#a0268ab7af75da31db9595dd5ccb7988d349d6f6" + integrity sha512-4DiYKqS0OTJxHJkAUx09Ni7AIL8T9wUKxtvqSKnQmBDcZfvxpSYJ4x7uorVjtejVSb6LI1hxnHJtyJ3SbrH31g== dependencies: "@antv/async-hook" "^2.2.9" - "@antv/l7-core" "2.22.0" - "@antv/l7-utils" "2.22.0" + "@antv/l7-core" "2.22.1" + "@antv/l7-utils" "2.22.1" "@babel/runtime" "^7.7.7" "@mapbox/geojson-rewind" "^0.5.2" "@mapbox/vector-tile" "^1.3.1" @@ -607,10 +607,10 @@ pbf "^3.2.1" supercluster "^7.0.0" -"@antv/l7-utils@2.22.0": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@antv/l7-utils/-/l7-utils-2.22.0.tgz#db86f853622c8419848462b8e1807f81707b3dca" - integrity sha512-uB8X87nGHy+LgoKtTY/6lm67K+Igq4qtBo9e09sTxGmfVcFESdl+N5Kug1YqxQoJA4ERTuTS2NqMqTD14edtZw== +"@antv/l7-utils@2.22.1": + version "2.22.1" + resolved "https://registry.yarnpkg.com/@antv/l7-utils/-/l7-utils-2.22.1.tgz#264d048aed567e64919a10298212f1954102f623" + integrity sha512-dJhLM3zJKxcihmcQ20wiEkDrWoshJhgtTLeigf8dtgp5TNeYHnaN8saulCOgSiBe8TI/UD1kFx9iFAV13PptoQ== dependencies: "@babel/runtime" "^7.7.7" "@turf/bbox" "^6.5.0" @@ -624,34 +624,34 @@ web-worker-helper "^0.0.3" "@antv/l7@^2.9.37": - version "2.22.0" - resolved "https://registry.yarnpkg.com/@antv/l7/-/l7-2.22.0.tgz#cbfedfde562373f380a2778a671b32f64fb5e5d4" - integrity sha512-9ZvrDC4HjE0DrY3zx/HlOQwp2enVt1ckgUz5Il9/K0axpHdQJlZIu+KJto4Y91krJgrw/Uam5aJFIudoUItGeg== - dependencies: - "@antv/l7-component" "2.22.0" - "@antv/l7-core" "2.22.0" - "@antv/l7-layers" "2.22.0" - "@antv/l7-maps" "2.22.0" - "@antv/l7-scene" "2.22.0" - "@antv/l7-source" "2.22.0" - "@antv/l7-utils" "2.22.0" + version "2.22.1" + resolved "https://registry.yarnpkg.com/@antv/l7/-/l7-2.22.1.tgz#ca70b6010ba70c44358fccce189ead4e351142da" + integrity sha512-MxJLzqy360PNXGaubkGaq4ScMDxqvmHodqEvBUSd7l/DSe2aZNUNiGnCOpTDWm1t9hOKnfvqsmQIWkQGfg9O0g== + dependencies: + "@antv/l7-component" "2.22.1" + "@antv/l7-core" "2.22.1" + "@antv/l7-layers" "2.22.1" + "@antv/l7-maps" "2.22.1" + "@antv/l7-scene" "2.22.1" + "@antv/l7-source" "2.22.1" + "@antv/l7-utils" "2.22.1" "@babel/runtime" "^7.7.7" -"@antv/l7plot-component@^0.0.10": - version "0.0.10" - resolved "https://registry.yarnpkg.com/@antv/l7plot-component/-/l7plot-component-0.0.10.tgz#9604e3f56daee4a7c19b965d57aac30007c64c0e" - integrity sha512-jnlg2Qm4tcUUuei+7fVk/HwRqhgxWnfdawOdB3C2u7YJqhkpk2mb7FPY+KGeZd+pqEQcAJTFi/1NeKpnZcP8kg== +"@antv/l7plot-component@^0.0.11": + version "0.0.11" + resolved "https://registry.yarnpkg.com/@antv/l7plot-component/-/l7plot-component-0.0.11.tgz#13ae7f60d542bf93e6028898ab87debd76caf521" + integrity sha512-aIl9K6I5MwMIU+Xxq/oOKXn0roAg5c73RjCXNmbTu8kxFEUUvCqU/35EDtfhWY7JKJeKnWRcl0fgDmTrD0t4iA== dependencies: "@antv/dom-util" "^2.0.3" "@antv/util" "^2.0.14" "@antv/l7plot@0.x": - version "0.5.10" - resolved "https://registry.yarnpkg.com/@antv/l7plot/-/l7plot-0.5.10.tgz#38a9d56e0d967fa1c669cf5b367e4e0752e662f1" - integrity sha512-thF/A7eLg8zL8jKNguayNmNBQ+FGq4Wg/20NA/j+6F5FEz/0SJlviK/7RxyK6TZDgR3zyIzD7tVLjZA0J+2s3w== + version "0.5.11" + resolved "https://registry.yarnpkg.com/@antv/l7plot/-/l7plot-0.5.11.tgz#d529aac105f26281dcbb33265032e6947db1576a" + integrity sha512-nSHyTzmbvQ8bq4eiD8L5hAPSZxZhmXRZ+j2Zi59iFy8hcR/XxV0N5RN8BkewiL1Zj7IPYXFtkAvBieEznae2UQ== dependencies: "@antv/event-emitter" "^0.1.2" - "@antv/l7plot-component" "^0.0.10" + "@antv/l7plot-component" "^0.0.11" "@antv/util" "^2.0.13" lodash-es "^4.17.21" topojson-client "^3.1.0" @@ -733,9 +733,9 @@ tslib "^2.0.3" "@antv/util@^3.3.2", "@antv/util@^3.3.4": - version "3.3.7" - resolved "https://registry.yarnpkg.com/@antv/util/-/util-3.3.7.tgz#35a900f0e9b65defbb92ec0e92a20b8189a18815" - integrity sha512-qqPg7rIPCsJyl7N56jAC25v/99mJ3ApVkgBsGijhiWrEeKvzXBPk1r5P77Pm9nCljpnn+hH8Z3t5AivbEoTJMg== + version "3.3.10" + resolved "https://registry.yarnpkg.com/@antv/util/-/util-3.3.10.tgz#6fb2560c0f42df61f824e1f995a1ed1bdb00eb9a" + integrity sha512-basGML3DFA3O87INnzvDStjzS+n0JLEhRnRsDzP9keiXz8gT1z/fTdmJAZFOzMMWxy+HKbi7NbSt0+8vz/OsBQ== dependencies: fast-deep-equal "^3.1.3" gl-matrix "^3.3.0" @@ -755,14 +755,14 @@ ua-parser-js "^0.7.20" "@antv/x6-react-shape@^1.4.5", "@antv/x6-react-shape@^1.5.2": - version "1.6.5" - resolved "https://registry.yarnpkg.com/@antv/x6-react-shape/-/x6-react-shape-1.6.5.tgz#0d9f44c300432505ea5be975c449ecf709558206" - integrity sha512-wdZWGluEuo1K9QIk0MSndiEcFtpHrMz6HlvswPQJhwxCiiEdC8q5eiP0zgPdfD1SjI3N3DUa8g2B93vrUJxQxw== + version "1.6.6" + resolved "https://registry.yarnpkg.com/@antv/x6-react-shape/-/x6-react-shape-1.6.6.tgz#58500fb177497324c7171ba16a3d6aefcf3c7868" + integrity sha512-+SIvQWeGhfH9miKDQvJT497iVDs/CcMwcgbNKbPV6qTUaSUeXjz/bZy8knbQ5t9XtkVYeQXZP7swiKK2xMI0UQ== "@antv/x6@^1.25.0", "@antv/x6@^1.30.1": - version "1.35.0" - resolved "https://registry.yarnpkg.com/@antv/x6/-/x6-1.35.0.tgz#331118e2f6ea2544e1582c1fb18d72ce865cf7f3" - integrity sha512-OwpGQelMc/zEOfJwaAvkJQ88JYEbyGKYOjI5RhHXTvGj5NTkZgOnNTzVx0RzcZRfUGgjZ7YPYprSKsxa9+/gfw== + version "1.35.1" + resolved "https://registry.yarnpkg.com/@antv/x6/-/x6-1.35.1.tgz#8592f8142095df38bad354d4ec59028b4922a1f2" + integrity sha512-XLFSGbcT/MOI883YKql9J/CqHUCPZxgwfel+sNN1eQbHA+JXYsGt0t9+IJ1qieaYAlxjgio5up+S9I0n+8QL/A== dependencies: csstype "^3.0.3" jquery "^3.5.1" @@ -1927,13 +1927,20 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.4", "@babel/runtime@^7.24.7", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.4", "@babel/runtime@^7.24.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.8.tgz#5d958c3827b13cc6d05e038c07fb2e5e3420d82e" integrity sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA== dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.1.2", "@babel/runtime@^7.7.7": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" + integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.24.7", "@babel/template@^7.3.3": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" @@ -3202,6 +3209,18 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/lodash-es@^4.17.12": + version "4.17.12" + resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.12.tgz#65f6d1e5f80539aa7cfbfc962de5def0cf4f341b" + integrity sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.17.9" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.9.tgz#0dc4902c229f6b8e2ac5456522104d7b1a230290" + integrity sha512-w9iWudx1XWOHW5lQRS9iKpK/XuRhnN+0T7HvdCCd802FYkT1AMTnxndJHGrNJwRoRHkslGr4S29tjm1cT7x/7w== + "@types/mapbox__point-geometry@*", "@types/mapbox__point-geometry@^0.1.4": version "0.1.4" resolved "https://registry.yarnpkg.com/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz#0ef017b75eedce02ff6243b4189210e2e6d5e56d" @@ -3616,9 +3635,9 @@ integrity sha512-cvvCMSZBT4VsRNtt0lI6XQqvOIIWw6+NRUtnPUMDVDgsI4pCZColz3qzF5QcP9wIYOHEc3jssIBse8UWONKhlQ== "@webgpu/types@^0.1.34": - version "0.1.44" - resolved "https://registry.yarnpkg.com/@webgpu/types/-/types-0.1.44.tgz#1b264c0bfcb298df59d0943dad8ef02b4ff98d14" - integrity sha512-JDpYJN5E/asw84LTYhKyvPpxGnD+bAKPtpW9Ilurf7cZpxaTbxkQcGwOd7jgB9BPBrTYQ+32ufo4HiuomTjHNQ== + version "0.1.46" + resolved "https://registry.yarnpkg.com/@webgpu/types/-/types-0.1.46.tgz#707d56d318e663b165a1133c5a86cfcc11afe59e" + integrity sha512-2iogO6Zh0pTbKLGZuuGWEmJpF/fTABGs7G9wXxpn7s24XSJchSUIiMqIJHURi5zsMZRRTuXrV/3GLOkmOFjq5w== "@xobotyi/scrollbar-width@^1.9.5": version "1.9.5" @@ -4077,7 +4096,12 @@ async-validator@^4.1.0: resolved "https://registry.yarnpkg.com/async-validator/-/async-validator-4.2.5.tgz#c96ea3332a521699d0afaaceed510a54656c6339" integrity sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg== -async@^3.1.1, async@^3.2.3: +async@^3.1.1: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +async@^3.2.3: version "3.2.5" resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== @@ -4104,6 +4128,18 @@ autoprefixer@^10.4.13: picocolors "^1.0.0" postcss-value-parser "^4.2.0" +autoprefixer@^10.4.20: + version "10.4.20" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b" + integrity sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g== + dependencies: + browserslist "^4.23.3" + caniuse-lite "^1.0.30001646" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.0.1" + postcss-value-parser "^4.2.0" + available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" @@ -4368,6 +4404,16 @@ browserslist@^4.0.0, browserslist@^4.18.1, browserslist@^4.21.10, browserslist@^ node-releases "^2.0.14" update-browserslist-db "^1.1.0" +browserslist@^4.23.3: + version "4.24.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" + integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== + dependencies: + caniuse-lite "^1.0.30001663" + electron-to-chromium "^1.5.28" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -4469,6 +4515,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001640: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz#6aa6610eb24067c246d30c57f055a9d0a7f8d05f" integrity sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA== +caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001663: + version "1.0.30001664" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz#d588d75c9682d3301956b05a3749652a80677df4" + integrity sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g== + case-sensitive-paths-webpack-plugin@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" @@ -5585,6 +5636,11 @@ electron-to-chromium@^1.4.820: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.827.tgz#76068ed1c71dd3963e1befc8ae815004b2da6a02" integrity sha512-VY+J0e4SFcNfQy19MEoMdaIcZLmDCprqvBtkii1WTCTQHpRvf5N8+3kTYCgL/PcntvwQvmMJWTuDPsq+IlhWKQ== +electron-to-chromium@^1.5.28: + version "1.5.29" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz#aa592a3caa95d07cc26a66563accf99fa573a1ee" + integrity sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw== + element-resize-detector@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.2.4.tgz#3e6c5982dd77508b5fa7e6d5c02170e26325c9b1" @@ -7024,7 +7080,7 @@ ini@^1.3.5: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -inline-style-prefixer@^7.0.0: +inline-style-prefixer@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz#9310f3cfa2c6f3901d1480f373981c02691781e8" integrity sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw== @@ -8706,15 +8762,15 @@ mz@^2.7.0: thenify-all "^1.0.0" nano-css@^5.3.1: - version "5.6.1" - resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.6.1.tgz#964120cb1af6cccaa6d0717a473ccd876b34c197" - integrity sha512-T2Mhc//CepkTa3X4pUhKgbEheJHYAxD0VptuqFhDbGMUWVV2m+lkNiW/Ieuj35wrfC8Zm0l7HvssQh7zcEttSw== + version "5.6.2" + resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.6.2.tgz#584884ddd7547278f6d6915b6805069742679a32" + integrity sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw== dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" css-tree "^1.1.2" csstype "^3.1.2" fastest-stable-stringify "^2.0.2" - inline-style-prefixer "^7.0.0" + inline-style-prefixer "^7.0.1" rtl-css-js "^1.16.1" stacktrace-js "^2.0.2" stylis "^4.3.0" @@ -8767,6 +8823,11 @@ node-releases@^2.0.14: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -9132,6 +9193,11 @@ picocolors@^1.0.0, picocolors@^1.0.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== +picocolors@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -9740,6 +9806,15 @@ postcss@^8.3.5, postcss@^8.4.23, postcss@^8.4.33, postcss@^8.4.4: picocolors "^1.0.1" source-map-js "^1.2.0" +postcss@^8.4.47: + version "8.4.47" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" + integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.0" + source-map-js "^1.2.1" + potpack@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14" @@ -11205,6 +11280,11 @@ source-map-js@^1.0.1, source-map-js@^1.2.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + source-map-loader@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-3.0.2.tgz#af23192f9b344daa729f6772933194cc5fa54fee" @@ -11568,11 +11648,16 @@ stylehacks@^5.1.1: browserslist "^4.21.4" postcss-selector-parser "^6.0.4" -stylis@^4.0.13, stylis@^4.3.0: +stylis@^4.0.13: version "4.3.2" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.2.tgz#8f76b70777dd53eb669c6f58c997bf0a9972e444" integrity sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg== +stylis@^4.3.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.4.tgz#ca5c6c4a35c4784e4e93a2a24dc4e9fa075250a4" + integrity sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now== + sucrase@^3.32.0: version "3.35.0" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" @@ -11709,6 +11794,34 @@ tailwindcss@^3.0.2: resolve "^1.22.2" sucrase "^3.32.0" +tailwindcss@^3.4.13: + version "3.4.13" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.13.tgz#3d11e5510660f99df4f1bfb2d78434666cb8f831" + integrity sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw== + dependencies: + "@alloc/quick-lru" "^5.2.0" + arg "^5.0.2" + chokidar "^3.5.3" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.3.0" + glob-parent "^6.0.2" + is-glob "^4.0.3" + jiti "^1.21.0" + lilconfig "^2.1.0" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.23" + postcss-import "^15.1.0" + postcss-js "^4.0.1" + postcss-load-config "^4.0.1" + postcss-nested "^6.0.1" + postcss-selector-parser "^6.0.11" + resolve "^1.22.2" + sucrase "^3.32.0" + tapable@^1.0.0: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" @@ -11936,11 +12049,16 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.5.3, tslib@^2.6.2: +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1: version "2.6.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +tslib@^2.5.3, tslib@^2.6.2: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -12059,9 +12177,9 @@ typewise@^1.0.3: typewise-core "^1.2.0" ua-parser-js@^0.7.20: - version "0.7.38" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.38.tgz#f497d8a4dc1fec6e854e5caa4b2f9913422ef054" - integrity sha512-fYmIy7fKTSFAhG3fuPlubeGaMoAd6r0rSnfEsO5nEY55i26KSLt9EH7PLQiiqPUhNqYIJvSkTy1oArIcXAbPbA== + version "0.7.39" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.39.tgz#c71efb46ebeabc461c4612d22d54f88880fabe7e" + integrity sha512-IZ6acm6RhQHNibSt7+c09hhvsKy9WUr4DVbeq9U8o71qxyYtJpQeDxQnMrVqnIFMLcQjHO0I9wgfO2vIahht4w== uglify-js@^2.6.2: version "2.8.29"