mirror of https://github.com/usememos/memos.git
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
|
import ForceGraph2D, { ForceGraphMethods, LinkObject, NodeObject } from "react-force-graph-2d";
|
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
|
import { cn } from "@/lib/utils";
|
|
import { extractMemoIdFromName } from "@/store/common";
|
|
import { Memo, MemoRelation_Type } from "@/types/proto/api/v1/memo_service";
|
|
import { LinkType, NodeType } from "./types";
|
|
import { convertMemoRelationsToGraphData } from "./utils";
|
|
|
|
interface Props {
|
|
memo: Memo;
|
|
className?: string;
|
|
parentPage?: string;
|
|
}
|
|
|
|
const MAIN_NODE_COLOR = "#14b8a6";
|
|
const DEFAULT_NODE_COLOR = "#a1a1aa";
|
|
|
|
const MemoRelationForceGraph = ({ className, memo, parentPage }: Props) => {
|
|
const navigateTo = useNavigateTo();
|
|
const [mode, setMode] = useState<"light" | "dark">("light");
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const graphRef = useRef<ForceGraphMethods<NodeObject<NodeType>, LinkObject<NodeType, LinkType>> | undefined>(undefined);
|
|
const [graphSize, setGraphSize] = useState({ width: 0, height: 0 });
|
|
|
|
// Simple dark mode detection
|
|
useEffect(() => {
|
|
const updateMode = () => {
|
|
const isDark = document.documentElement.classList.contains("dark");
|
|
setMode(isDark ? "dark" : "light");
|
|
};
|
|
|
|
updateMode();
|
|
|
|
// Watch for changes to the dark class
|
|
const observer = new MutationObserver(updateMode);
|
|
observer.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: ["class"],
|
|
});
|
|
|
|
return () => observer.disconnect();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!containerRef.current) return;
|
|
setGraphSize(containerRef.current.getBoundingClientRect());
|
|
}, []);
|
|
|
|
const onNodeClick = (node: NodeObject<NodeType>) => {
|
|
if (node.memo.name === memo.name) return;
|
|
navigateTo(`/${memo.name}`, {
|
|
state: {
|
|
from: parentPage,
|
|
},
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div ref={containerRef} className={cn("opacity-80", className)}>
|
|
<ForceGraph2D
|
|
ref={graphRef}
|
|
width={graphSize.width}
|
|
height={graphSize.height}
|
|
enableZoomInteraction
|
|
cooldownTicks={0}
|
|
nodeColor={(node) => (node.id === memo.name ? MAIN_NODE_COLOR : DEFAULT_NODE_COLOR)}
|
|
nodeRelSize={3}
|
|
nodeLabel={(node) => extractMemoIdFromName(node.memo.name).slice(0, 6).toLowerCase()}
|
|
linkColor={() => (mode === "light" ? "#e4e4e7" : "#3f3f46")}
|
|
graphData={convertMemoRelationsToGraphData(memo.relations.filter((r) => r.type === MemoRelation_Type.REFERENCE))}
|
|
onNodeClick={onNodeClick}
|
|
linkDirectionalArrowLength={3}
|
|
linkDirectionalArrowRelPos={1}
|
|
linkCurvature={0.25}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default MemoRelationForceGraph;
|