123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- import React from "react";
- import { ReactZoomPanPinchRef, TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
- import { Canvas, Edge, ElkRoot } from "reaflow";
- import { CustomNode } from "src/components/CustomNode";
- import useGraph from "src/store/useGraph";
- import useUser from "src/store/useUser";
- import { getNodePath } from "src/utils/getNodePath";
- import styled from "styled-components";
- import { Loading } from "../Loading";
- import { ErrorView } from "./ErrorView";
- import { PremiumView } from "./PremiumView";
- interface GraphProps {
- isWidget?: boolean;
- openNodeModal: () => void;
- }
- const StyledEditorWrapper = styled.div<{ widget: boolean }>`
- position: absolute;
- width: 100%;
- height: ${({ widget }) => (widget ? "calc(100vh - 36px)" : "calc(100vh - 65px)")};
- background: ${({ theme }) => theme.BACKGROUND_SECONDARY};
- background-image: ${({ theme }) =>
- `radial-gradient(#505050 1px, ${theme.BACKGROUND_SECONDARY} 1px)`};
- background-size: 25px 25px;
- :active {
- cursor: move;
- }
- .dragging,
- .dragging button {
- pointer-events: none;
- }
- rect {
- fill: ${({ theme }) => theme.BACKGROUND_NODE};
- }
- @media only screen and (max-width: 1440px) {
- background-image: ${({ theme }) =>
- `radial-gradient(#505050 0.5px, ${theme.BACKGROUND_SECONDARY} 0.5px)`};
- background-size: 15px 15px;
- }
- @media only screen and (max-width: 320px) {
- height: 100vh;
- }
- `;
- const GraphComponent = ({ isWidget = false, openNodeModal }: GraphProps) => {
- const isPremium = useUser(state => state.isPremium);
- const setLoading = useGraph(state => state.setLoading);
- const setZoomPanPinch = useGraph(state => state.setZoomPanPinch);
- const centerView = useGraph(state => state.centerView);
- const setSelectedNode = useGraph(state => state.setSelectedNode);
- const loading = useGraph(state => state.loading);
- const direction = useGraph(state => state.direction);
- const nodes = useGraph(state => state.nodes);
- const edges = useGraph(state => state.edges);
- const [size, setSize] = React.useState({
- width: 1,
- height: 1,
- });
- const handleNodeClick = React.useCallback(
- (_: React.MouseEvent<SVGElement>, data: NodeData) => {
- if (setSelectedNode)
- setSelectedNode({ node: data.text, path: getNodePath(nodes, edges, data.id) });
- if (openNodeModal) openNodeModal();
- },
- [edges, nodes, openNodeModal, setSelectedNode]
- );
- const onInit = React.useCallback(
- (ref: ReactZoomPanPinchRef) => {
- setZoomPanPinch(ref);
- },
- [setZoomPanPinch]
- );
- const onLayoutChange = React.useCallback(
- (layout: ElkRoot) => {
- if (layout.width && layout.height) {
- const areaSize = layout.width * layout.height;
- const changeRatio = Math.abs((areaSize * 100) / (size.width * size.height) - 100);
- setSize({
- width: (layout.width as number) + 400,
- height: (layout.height as number) + 400,
- });
- requestAnimationFrame(() => {
- setTimeout(() => {
- setLoading(false);
- setTimeout(() => {
- if (changeRatio > 70 || isWidget) centerView();
- });
- });
- });
- }
- },
- [centerView, isWidget, setLoading, size.height, size.width]
- );
- const onCanvasClick = React.useCallback(() => {
- const input = document.querySelector("input:focus") as HTMLInputElement;
- if (input) input.blur();
- }, []);
- if (nodes.length > 8_000) return <ErrorView />;
- if (nodes.length > 1_000 && !isWidget) {
- if (!isPremium()) return <PremiumView />;
- }
- return (
- <StyledEditorWrapper onContextMenu={e => e.preventDefault()} widget={isWidget}>
- <Loading message="Painting graph..." loading={loading} />
- <TransformWrapper
- maxScale={2}
- minScale={0.05}
- initialScale={0.4}
- wheel={{ step: 0.08 }}
- zoomAnimation={{ animationType: "linear" }}
- doubleClick={{ disabled: true }}
- onInit={onInit}
- onPanning={ref => ref.instance.wrapperComponent?.classList.add("dragging")}
- onPanningStop={ref => ref.instance.wrapperComponent?.classList.remove("dragging")}
- >
- <TransformComponent
- wrapperStyle={{
- width: "100%",
- height: "100%",
- overflow: "hidden",
- display: loading ? "none" : "block",
- }}
- >
- <Canvas
- className="jsoncrack-canvas"
- nodes={nodes}
- edges={edges}
- maxWidth={size.width}
- maxHeight={size.height}
- direction={direction}
- onLayoutChange={onLayoutChange}
- onCanvasClick={onCanvasClick}
- zoomable={false}
- animated={false}
- readonly={true}
- dragEdge={null}
- dragNode={null}
- fit={true}
- key={direction}
- node={props => <CustomNode {...props} onClick={handleNodeClick} />}
- edge={props => <Edge {...props} containerClassName={`edge-${props.id}`} />}
- />
- </TransformComponent>
- </TransformWrapper>
- </StyledEditorWrapper>
- );
- };
- export const Graph = React.memo(GraphComponent);
|