From d3e024751352bd745cd897ca6e73f9b110e0ac90 Mon Sep 17 00:00:00 2001 From: Cheslav Zhuravsky Date: Thu, 19 Sep 2024 16:29:22 +0700 Subject: [PATCH] feat(graph): improvements (#1295) --- .../CyberlinksGraph/CyberlinksGraph.tsx | 20 ++- .../CyberlinksGraphContainer.tsx | 11 +- .../GraphFullscreenBtn.module.scss | 10 -- .../GraphFullscreenBtn/GraphFullscreenBtn.tsx | 18 ++- .../cyberlinks/GraphNew/GraphNew.module.scss | 8 -- src/features/cyberlinks/GraphNew/GraphNew.tsx | 126 ++++++++++-------- .../graph/GraphActionBar/GraphActionBar.tsx | 91 +++++++++++++ src/features/cyberlinks/hooks/useCyberlink.ts | 2 - src/layouts/Main.tsx | 11 +- src/pages/Brain/Brain.tsx | 40 +----- src/pages/robot/Brain/Brain.tsx | 11 +- src/pages/robot/Brain/ui/GraphView.tsx | 15 ++- src/pages/robot/Brain/useGraphLimit.ts | 8 ++ src/pages/robot/Layout/Layout.tsx | 6 +- 14 files changed, 238 insertions(+), 139 deletions(-) create mode 100644 src/features/cyberlinks/graph/GraphActionBar/GraphActionBar.tsx diff --git a/src/features/cyberlinks/CyberlinksGraph/CyberlinksGraph.tsx b/src/features/cyberlinks/CyberlinksGraph/CyberlinksGraph.tsx index c714896ea..0ec2064f1 100644 --- a/src/features/cyberlinks/CyberlinksGraph/CyberlinksGraph.tsx +++ b/src/features/cyberlinks/CyberlinksGraph/CyberlinksGraph.tsx @@ -3,6 +3,7 @@ import { ForceGraph3D } from 'react-force-graph'; import GraphHoverInfo from './GraphHoverInfo/GraphHoverInfo'; import styles from './CyberlinksGraph.module.scss'; +import GraphActionBar from '../graph/GraphActionBar/GraphActionBar'; type Props = { data: any; @@ -16,7 +17,7 @@ const DEFAULT_CAMERA_DISTANCE = 1300; const CAMERA_ZOOM_IN_EFFECT_DURATION = 5000; const CAMERA_ZOOM_IN_EFFECT_DELAY = 500; -function CyberlinksGraph({ data, size }: Props) { +function CyberlinksGraph({ data, size, minVersion }: Props) { const [isRendering, setRendering] = useState(true); const [touched, setTouched] = useState(false); const [hoverNode, setHoverNode] = useState(null); @@ -173,7 +174,7 @@ function CyberlinksGraph({ data, size }: Props) { ref={fgRef} graphData={data} showNavInfo={false} - backgroundColor="#000000" + backgroundColor="rgba(0, 0, 0, 0)" warmupTicks={420} cooldownTicks={0} enableNodeDrag={false} @@ -210,11 +211,16 @@ function CyberlinksGraph({ data, size }: Props) { onEngineStop={handleEngineStop} /> - + {!minVersion && ( + <> + + + + )} ); } diff --git a/src/features/cyberlinks/CyberlinksGraph/CyberlinksGraphContainer.tsx b/src/features/cyberlinks/CyberlinksGraph/CyberlinksGraphContainer.tsx index 8d878383d..9d30df49e 100644 --- a/src/features/cyberlinks/CyberlinksGraph/CyberlinksGraphContainer.tsx +++ b/src/features/cyberlinks/CyberlinksGraph/CyberlinksGraphContainer.tsx @@ -2,11 +2,11 @@ import { createPortal } from 'react-dom'; import { Loading } from 'src/components'; import { useAppSelector } from 'src/redux/hooks'; import { selectCurrentAddress } from 'src/redux/features/pocket'; +import { Stars } from 'src/containers/portal/components'; import useCyberlinks from './useCyberlinks'; import { PORTAL_ID } from '../../../containers/application/App'; import GraphNew from '../GraphNew/GraphNew'; import CyberlinksGraph from './CyberlinksGraph'; -import GraphFullscreenBtn from '../GraphFullscreenBtn/GraphFullscreenBtn'; enum Types { '3d' = '3d', @@ -20,6 +20,9 @@ type Props = { limit?: number | false; data?: any; type?: Types; + + // temp + minVersion?: boolean; }; function CyberlinksGraphContainer({ @@ -28,6 +31,7 @@ function CyberlinksGraphContainer({ size, limit, data, + minVersion, type = Types['2d'], }: Props) { const { data: fetchData, loading } = useCyberlinks( @@ -42,6 +46,8 @@ function CyberlinksGraphContainer({ const Comp = type === Types['2d'] ? GraphNew : CyberlinksGraph; + // const { isFullscreen } = useFullscreen(); + const content = loading ? (
) : ( <> - + {!minVersion && } diff --git a/src/features/cyberlinks/GraphFullscreenBtn/GraphFullscreenBtn.module.scss b/src/features/cyberlinks/GraphFullscreenBtn/GraphFullscreenBtn.module.scss index 45058786f..71fb70287 100644 --- a/src/features/cyberlinks/GraphFullscreenBtn/GraphFullscreenBtn.module.scss +++ b/src/features/cyberlinks/GraphFullscreenBtn/GraphFullscreenBtn.module.scss @@ -1,12 +1,2 @@ .btn { - position: absolute; - right: 50px; - z-index: 10; - top: 200px; - font-size: 18px; - color: var(--primary-color); - - &:hover { - opacity: 0.7; - } } diff --git a/src/features/cyberlinks/GraphFullscreenBtn/GraphFullscreenBtn.tsx b/src/features/cyberlinks/GraphFullscreenBtn/GraphFullscreenBtn.tsx index 0fa29ad72..5329e1f74 100644 --- a/src/features/cyberlinks/GraphFullscreenBtn/GraphFullscreenBtn.tsx +++ b/src/features/cyberlinks/GraphFullscreenBtn/GraphFullscreenBtn.tsx @@ -1,6 +1,7 @@ import { PORTAL_ID } from 'src/containers/application/App'; import { useState } from 'react'; import useEventListener from 'src/hooks/dom/useEventListener'; +import { Button } from 'src/components'; import styles from './GraphFullscreenBtn.module.scss'; export function useFullscreen() { @@ -20,6 +21,19 @@ export function useFullscreen() { } } + function handleKeyDown(event: KeyboardEvent) { + // if input is focused, do not handle keydown + if (['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName)) { + return; + } + if (event.key === 'f') { + toggleFullscreen(document.getElementById(PORTAL_ID)); + } + } + + // listen F key + useEventListener('keydown', handleKeyDown); + return { isFullscreen, toggleFullscreen, @@ -38,9 +52,9 @@ function GraphFullscreenBtn() { } return ( - + ); } diff --git a/src/features/cyberlinks/GraphNew/GraphNew.module.scss b/src/features/cyberlinks/GraphNew/GraphNew.module.scss index 375558b3c..60bd0bcb1 100644 --- a/src/features/cyberlinks/GraphNew/GraphNew.module.scss +++ b/src/features/cyberlinks/GraphNew/GraphNew.module.scss @@ -6,11 +6,3 @@ height: 100%; position: relative; } - -.total { - top: 250px; - color: white; - left: calc(50% - 50px); - position: absolute; - z-index: 11; -} diff --git a/src/features/cyberlinks/GraphNew/GraphNew.tsx b/src/features/cyberlinks/GraphNew/GraphNew.tsx index a65e1546d..e15ed251f 100644 --- a/src/features/cyberlinks/GraphNew/GraphNew.tsx +++ b/src/features/cyberlinks/GraphNew/GraphNew.tsx @@ -4,25 +4,31 @@ import { CosmographProvider, Cosmograph, CosmographRef, - CosmographSearchRef, } from '@cosmograph/react'; import { CosmosInputNode, CosmosInputLink } from '@cosmograph/cosmos'; -import { ActionBar as ActionBarComponent } from 'src/components'; +import { Button } from 'src/components'; +import useAdviserTexts from 'src/features/adviser/useAdviserTexts'; +import useGraphLimit from 'src/pages/robot/Brain/useGraphLimit'; +import { useLocation } from 'react-router-dom'; import { Node } from './data'; -// import './styles.css'; import styles from './GraphNew.module.scss'; -import { useFullscreen } from '../GraphFullscreenBtn/GraphFullscreenBtn'; import { useCyberlinkWithWaitAndAdviser } from '../hooks/useCyberlink'; import GraphHoverInfo from '../CyberlinksGraph/GraphHoverInfo/GraphHoverInfo'; +import GraphActionBar from '../graph/GraphActionBar/GraphActionBar'; export default function GraphNew({ address, data, size }) { const cosmograph = useRef(); // const histogram = useRef>(); // const timeline = useRef>(); - const search = useRef(); + // const search = useRef(); + + const location = useLocation(); + const [degree, setDegree] = useState([]); + const { limit } = useGraphLimit(); + // max 2 nodes const [selectedNodes, setSelectedNodes] = useState([]); @@ -36,27 +42,7 @@ export default function GraphNew({ address, data, size }) { const [hoverNode, setHoverNode] = useState(null); const [nodePostion, setNodePostion] = useState(null); - - const { links, nodes } = useMemo(() => { - const nodes = [...data.nodes, ...localData.nodes].map((node) => { - return { - ...node, - size: 0.5, - // value: 1, - color: node.color || 'rgba(0,100,235,1)', - }; - }); - - const links = [...data.links, ...localData.links].map((link) => { - return { - ...link, - width: 2.5, - color: link.color || 'rgba(9,255,13,1)', - }; - }); - - return { links, nodes }; - }, [data, localData]); + const [selectedNode, setSelectedNode] = useState(); const scaleColor = useRef( scaleSymlog() @@ -72,6 +58,14 @@ export default function GraphNew({ address, data, size }) { } }, [degree]); + useEffect(() => { + const timeoutId = setTimeout(() => { + cosmograph.current?.pause(); + }, 5000); + + return () => clearTimeout(timeoutId); + }, []); + // const nodeColor = useCallback( // (n: Node, index: number) => { // if (index === undefined) { @@ -90,15 +84,6 @@ export default function GraphNew({ address, data, size }) { // const [showLabelsFor, setShowLabelsFor] = useState( // undefined // ); - const [selectedNode, setSelectedNode] = useState(); - - useEffect(() => { - const timeoutId = setTimeout(() => { - cosmograph.current?.pause(); - }, 5000); - - return () => clearTimeout(timeoutId); - }, []); // const onCosmographClick = useCallback< // Exclude['onClick'], undefined> @@ -143,18 +128,43 @@ export default function GraphNew({ address, data, size }) { }); } - console.log(localData); + const { links, nodes } = useMemo(() => { + const nodes = [...data.nodes, ...localData.nodes].map((node) => { + return { + ...node, + size: 0.5, + // value: 1, + color: node.color || 'rgba(0,100,235,1)', + }; + }); - const { isFullscreen } = useFullscreen(); + const links = [...data.links, ...localData.links].map((link) => { + return { + ...link, + width: 2.5, + color: link.color || 'rgba(9,255,13,1)', + }; + }); + + return { links, nodes }; + }, [data, localData]); + + useAdviserTexts({ + defaultText: useMemo(() => { + return ( + <> + {/* @nick (or) your */} + public brain, with {nodes.length} particles and {links.length}{' '} + cyberlinks +
+ The limit is {limit} + + ); + }, [nodes.length, links.length, limit]), + }); return (
- {!isFullscreen && ( -
-

total nodes: {nodes.length}

-

total links: {links.length}

-
- )} */} - + {location.pathname !== '/brain' && ( + + + + )}
); } @@ -275,22 +289,22 @@ function ActionBar({ selectedNodes, callback }: Props2) { callback, }); + const { length } = selectedNodes; + let text; - if (selectedNodes.length !== 2 || selectedNodes.length === 0) { - text = `select ${2 - selectedNodes.length} particles`; + if (length !== 2 || length === 0) { + text = `select ${2 - length} particle${length === 0 ? 's' : ''}`; } else { - text = ''; + text = 'cyberlink particles'; } return ( - + ); } diff --git a/src/features/cyberlinks/graph/GraphActionBar/GraphActionBar.tsx b/src/features/cyberlinks/graph/GraphActionBar/GraphActionBar.tsx new file mode 100644 index 000000000..44d70c399 --- /dev/null +++ b/src/features/cyberlinks/graph/GraphActionBar/GraphActionBar.tsx @@ -0,0 +1,91 @@ +import { + ActionBar as ActionBarComponent, + Button, + InputNumber, +} from 'src/components'; +import { useState } from 'react'; +import useGraphLimit from 'src/pages/robot/Brain/useGraphLimit'; +import useAdviserTexts from 'src/features/adviser/useAdviserTexts'; +import GraphFullscreenBtn, { + useFullscreen, +} from '../../GraphFullscreenBtn/GraphFullscreenBtn'; + +enum Steps { + INITIAL, + CHANGE_LIMIT, +} + +function GraphActionBar({ children }: { children?: React.ReactNode }) { + const [step, setStep] = useState(Steps.INITIAL); + const { isFullscreen } = useFullscreen(); + + const { limit, setLimit } = useGraphLimit(); + const [newLimit, setNewLimit] = useState(limit); + + let content; + let adviserText; + let onClickBack; + + switch (step) { + case Steps.INITIAL: { + content = ( + <> + {!isFullscreen && ( + <> + {children} + + + + )} + + + + ); + + break; + } + + case Steps.CHANGE_LIMIT: { + content = ( + <> + setNewLimit(Number(value))} + placeholder="Enter new limit" + /> + + + + ); + + onClickBack = () => setStep(Steps.INITIAL); + + adviserText = + 'change limit to show in graph, but it can reduce performance'; + + break; + } + + default: { + } + } + + useAdviserTexts({ + defaultText: adviserText, + }); + + return ( + {content} + ); +} + +export default GraphActionBar; diff --git a/src/features/cyberlinks/hooks/useCyberlink.ts b/src/features/cyberlinks/hooks/useCyberlink.ts index d396c1406..68aa7a447 100644 --- a/src/features/cyberlinks/hooks/useCyberlink.ts +++ b/src/features/cyberlinks/hooks/useCyberlink.ts @@ -90,8 +90,6 @@ export function useCyberlinkWithWaitAndAdviser({ to, from, callback }: Props2) { }, }); - console.log(waitForTx); - const isLoading = addToIPFSTo.isLoading || addToIPFSFrom.isLoading || diff --git a/src/layouts/Main.tsx b/src/layouts/Main.tsx index f7558cc29..6da44efb4 100644 --- a/src/layouts/Main.tsx +++ b/src/layouts/Main.tsx @@ -8,7 +8,7 @@ import CyberlinksGraphContainer from 'src/features/cyberlinks/CyberlinksGraph/Cy import TimeFooter from 'src/features/TimeFooter/TimeFooter'; import { Networks } from 'src/types/networks'; import { CHAIN_ID } from 'src/constants/config'; -import { Link } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import CircularMenu from 'src/components/appMenu/CircularMenu/CircularMenu'; import TimeHistory from 'src/features/TimeHistory/TimeHistory'; import MobileMenu from 'src/components/appMenu/MobileMenu/MobileMenu'; @@ -27,12 +27,16 @@ function MainLayout({ children }: { children: JSX.Element }) { const dispatch = useAppDispatch(); const [isRenderGraph, setIsRenderGraph] = useState(false); + const location = useLocation(); + const graphSize = Math.min(viewportWidth * 0.13, 220); const isMobile = viewportWidth <= Number(stylesOracle.mobileBreakpoint.replace('px', '')); useEffect(() => { - dispatch(setFocus(true)); + if (!location.pathname.includes('brain')) { + dispatch(setFocus(true)); + } const timeout = setTimeout(() => { setIsRenderGraph(true); @@ -41,7 +45,7 @@ function MainLayout({ children }: { children: JSX.Element }) { return () => { clearTimeout(timeout); }; - }, [dispatch]); + }, [dispatch, location]); useEffect(() => { if (!ref.current) { @@ -80,6 +84,7 @@ function MainLayout({ children }: { children: JSX.Element }) { {isRenderGraph && ( diff --git a/src/pages/Brain/Brain.tsx b/src/pages/Brain/Brain.tsx index 73e8bbae2..d805be6ce 100644 --- a/src/pages/Brain/Brain.tsx +++ b/src/pages/Brain/Brain.tsx @@ -1,11 +1,12 @@ import CyberlinksGraphContainer from 'src/features/cyberlinks/CyberlinksGraph/CyberlinksGraphContainer'; -import { ActionBar, Button } from 'src/components'; +import { Button } from 'src/components'; import useAdviserTexts from 'src/features/adviser/useAdviserTexts'; +import GraphActionBar from 'src/features/cyberlinks/graph/GraphActionBar/GraphActionBar'; import styles from './Brain.module.scss'; import useGraphLimit from '../robot/Brain/useGraphLimit'; function Brain() { - const { limit, setSearchParams } = useGraphLimit(); + const { limit } = useGraphLimit(); useAdviserTexts({ defaultText: 'cyber graph', @@ -15,47 +16,16 @@ function Brain() {
- - - + - +
); } export default Brain; - -export function ParamsBlock({ limit, setSearchParams }) { - return ( -

- Limit is: {limit.toLocaleString()} -
- use url param to set different limit : - { - e.preventDefault(); - - setSearchParams({ - limit: 500, - }); - }} - > - /brain?limit=500 - -

- ); -} diff --git a/src/pages/robot/Brain/Brain.tsx b/src/pages/robot/Brain/Brain.tsx index 6d93cd2b9..8d8df016c 100644 --- a/src/pages/robot/Brain/Brain.tsx +++ b/src/pages/robot/Brain/Brain.tsx @@ -3,7 +3,6 @@ import { Route, Routes, useParams } from 'react-router-dom'; import { useMemo } from 'react'; import useAdviserTexts from 'src/features/adviser/useAdviserTexts'; import CyberlinksGraphContainer from 'src/features/cyberlinks/CyberlinksGraph/CyberlinksGraphContainer'; -import { ParamsBlock } from 'src/pages/Brain/Brain'; import { useRobotContext } from '../robot.context'; import TreedView from './ui/TreedView'; import styles from './Brain.module.scss'; @@ -82,13 +81,7 @@ function Brain() { export default Brain; function Graph2d({ address }) { - const { limit, setSearchParams } = useGraphLimit(); + const { limit } = useGraphLimit(); - return ( -
- - - -
- ); + return ; } diff --git a/src/pages/robot/Brain/ui/GraphView.tsx b/src/pages/robot/Brain/ui/GraphView.tsx index 2c024d411..c3c323187 100644 --- a/src/pages/robot/Brain/ui/GraphView.tsx +++ b/src/pages/robot/Brain/ui/GraphView.tsx @@ -1,13 +1,22 @@ import CyberlinksGraphContainer from 'src/features/cyberlinks/CyberlinksGraph/CyberlinksGraphContainer'; -import { ParamsBlock } from 'src/pages/Brain/Brain'; import useGraphLimit from '../useGraphLimit'; function GraphView({ address }: { address?: string }) { - const { limit, setSearchParams } = useGraphLimit(500); + const { limit } = useGraphLimit(500); return (
- +

+ Limit is: {limit.toLocaleString()} +

+ { + setSearchParams({ limit }); + }, [limit]); + return { limit, setSearchParams, + setLimit: (limit: number) => { + setSearchParams({ limit }); + }, }; } diff --git a/src/pages/robot/Layout/Layout.tsx b/src/pages/robot/Layout/Layout.tsx index db6d7db8f..f13b10cb2 100644 --- a/src/pages/robot/Layout/Layout.tsx +++ b/src/pages/robot/Layout/Layout.tsx @@ -1,4 +1,4 @@ -import { Outlet } from 'react-router-dom'; +import { Outlet, useLocation } from 'react-router-dom'; import Loader2 from 'src/components/ui/Loader2'; import { MainContainer } from 'src/components'; import { useRobotContext } from '../robot.context'; @@ -9,6 +9,8 @@ import RobotHeader from './RobotHeader/RobotHeader'; function Layout() { const { address, isLoading, nickname, isOwner } = useRobotContext(); + const location = useLocation(); + const counts = useMenuCounts(address); const title = `robot ${nickname || address || ''}`; @@ -22,7 +24,7 @@ function Layout() { {!isOwner && } - + {!location.pathname.includes('brain') && } )}