/* eslint-disable @typescript-eslint/no-shadow */ /* eslint-disable no-shadow */ /* eslint-disable @eslint-react/no-array-index-key */ /* eslint-disable sonarjs/no-small-switch */ /* eslint-disable react-perf/jsx-no-new-function-as-prop */ /* eslint-disable react-perf/jsx-no-new-object-as-prop */ import { dia, linkTools, shapes } from '@joint/core'; import { PAPER_CLASSNAME, LIGHT } from 'storybook-config/theme'; import './index.css'; import { createElements, createLinks, GraphProvider, Highlighter, MeasuredNode, Paper, Port, useCellId, useElements, useGraph, useLinks, usePaper, useUpdateElement, type GraphElement, type PaperProps, type RenderElement, } from '@joint/react'; import { useCallback, useState } from 'react'; import { ShowJson } from 'storybook-config/decorators/with-simple-data'; import { PaperProvider } from '../../../components/paper-provider/paper-provider'; // Define types for the elements interface ElementBase extends GraphElement { readonly elementType: 'alert' | 'info' | 'table'; } interface MessageElement extends ElementBase { readonly elementType: 'alert' | 'info'; readonly title: string; readonly description: string; readonly inputText: string; } interface TableElement extends ElementBase { readonly elementType: 'table'; readonly columnNames: string[]; readonly rows: string[][]; } type Element = MessageElement | TableElement; type ElementWithSelected = { readonly isSelected: boolean } & T; const BUTTON_CLASSNAME = 'bg-blue-500 cursor-pointer hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-sm flex items-center'; // Define static properties for the paper - used by minimap and main paper const PAPER_PROPS: PaperProps = { defaultRouter: { name: 'rightAngle', args: { margin: 25, }, }, defaultConnector: { name: 'straight', args: { cornerType: 'line', cornerPreserveAspectRatio: true }, }, snapLinks: { radius: 25 }, validateMagnet: (_cellView, magnet) => { return magnet.getAttribute('magnet') !== 'passive'; }, sorting: dia.Paper.sorting.APPROX, linkPinning: false, onLinkMouseEnter: ({ linkView }) => linkView.addTools(toolsView), onLinkMouseLeave: ({ linkView }) => linkView.removeTools(), }; // Create initial elements and links with typing support const elements = createElements([ { id: '1', x: 50, y: 110, elementType: 'alert', title: 'This is error element', description: 'This is longer text, it can be any message provided by the user', inputText: 'Node Text', }, { id: '2', x: 550, y: 110, elementType: 'info', title: 'This is info element', description: 'This is longer text, it can be any message provided by the user', inputText: '', }, { id: '3', x: 50, y: 370, elementType: 'table', columnNames: ['Column 1', 'Column 2', 'Column 3'], rows: [ ['Row 1', 'Row 2', 'Row 3'], ['Row 4', 'Row 5', 'Row 6'], ['Row 7', 'Row 8', 'Row 9'], ], inputText: '', width: 400, height: 200, attrs: { root: { magnet: false, }, }, }, ]); // Create initial links from table element port to another element const links = createLinks([ { id: 'link2', source: { id: '3', port: 'out-3-0' }, // Port from table element target: { id: '1' }, attrs: { line: { stroke: LIGHT, class: 'link', strokeWidth: 2, strokeDasharray: '5,5', targetMarker: { d: `M 0 0 L 8 4 L 8 -4 Z`, // Larger arrowhead }, }, }, }, ]); // Define the message component function MessageComponent({ elementType, title, description, inputText, width, height, isSelected, }: ElementWithSelected) { let iconContent; let titleText; switch (elementType) { case 'alert': { iconContent = ( ); titleText = {title}; break; } default: { iconContent = ; titleText = {title}; break; } } const id = useCellId(); const setMessage = useUpdateElement(id, 'inputText'); return (
{iconContent}
{titleText}
{description}
{/* Divider */}
{ setMessage(value); }} />
); } const ROW_HEIGHT = 45; const ROW_START = 65; // Define the table element function TableElement({ columnNames, rows, width, height, isSelected, }: ElementWithSelected) { const cellId = useCellId(); return ( <>
{columnNames.map((name) => ( ))} {rows.map((row, index) => ( {row.map((cell, cellIndex) => ( ))} ))}
{name}
{cell}
{rows.map((_, index) => (
))}
); } // Minimap component function MiniMap() { const renderElement: RenderElement = useCallback( ({ width, height }) => , [] ); // On change, the minimap will be resized to fit the content automatically const onElementReady = useCallback(({ paper }: { paper: dia.Paper }) => { const { model: graph } = paper; const contentArea = graph.getCellsBBox(graph.getElements()); if (!contentArea) { return; } paper.transformToFitContent({ contentArea, verticalAlign: 'middle', horizontalAlign: 'middle', padding: 20, }); }, []); return (
); } // Define the remove tool for the link const removeTool = new linkTools.Remove({ scale: 1.5, style: { stroke: '#999' }, }); // Define the tools view for the link - so we can remove the link when hovered const toolsView = new dia.ToolsView({ tools: [removeTool], }); interface ToolbarProps { readonly onToggleMinimap: (visible: boolean) => void; readonly isMinimapVisible: boolean; readonly selectedId: dia.Cell.ID | null; readonly setSelectedId: (id: dia.Cell.ID | null) => void; readonly showElementsInfo: boolean; readonly setShowElementsInfo: (show: boolean) => void; } // Toolbar component with some actions function ToolBar(props: Readonly) { const { onToggleMinimap, isMinimapVisible, selectedId, setSelectedId, setShowElementsInfo, showElementsInfo, } = props; const graph = useGraph(); const paper = usePaper(); return (
); } // Show elements and links info function ElementsInfo() { const elements = useElements(); const links = useLinks(); return (
Elements
Links
); } // Define main paper component and render elements function Main() { const [isMinimapVisible, setIsMinimapVisible] = useState(false); const [selectedElement, setSelectedElement] = useState(null); const [showElementsInfo, setShowElementsInfo] = useState(false); const renderElement = useCallback( (element: Element) => { const { elementType, id } = element; const isSelected = id === selectedElement; switch (elementType) { case 'alert': case 'info': { return ; } case 'table': { return ; } } }, [selectedElement] ); return (
{ const cell = cellView.model; setSelectedElement(cell.id ?? null); }} onLinkPointerClick={() => { setSelectedElement(null); }} onBlankPointerClick={() => { setSelectedElement(null); }} /> {isMinimapVisible && }
{showElementsInfo && }
); } export default function App() { return (
); }