From dc7730cf5cb2eeb16e0d335730af9055016e6b76 Mon Sep 17 00:00:00 2001 From: stone Date: Thu, 6 Feb 2025 15:34:34 +0800 Subject: [PATCH 01/16] feat: add export and import icons --- .../src/components/DataStory/icons/export.tsx | 18 ++++++++++++++++++ .../components/DataStory/icons/importIcon.tsx | 15 +++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 packages/ui/src/components/DataStory/icons/export.tsx create mode 100644 packages/ui/src/components/DataStory/icons/importIcon.tsx diff --git a/packages/ui/src/components/DataStory/icons/export.tsx b/packages/ui/src/components/DataStory/icons/export.tsx new file mode 100644 index 00000000..b8b37bbd --- /dev/null +++ b/packages/ui/src/components/DataStory/icons/export.tsx @@ -0,0 +1,18 @@ +export const ExportIcon: React.FC = () => { + return ( + + + + + ); +}; \ No newline at end of file diff --git a/packages/ui/src/components/DataStory/icons/importIcon.tsx b/packages/ui/src/components/DataStory/icons/importIcon.tsx new file mode 100644 index 00000000..648e548f --- /dev/null +++ b/packages/ui/src/components/DataStory/icons/importIcon.tsx @@ -0,0 +1,15 @@ +// Icon depends on stroke control +export const ImportIcon: React.FC = () => { + return ( + + + ); +}; \ No newline at end of file From eeb7182da068fc0d5ce399a96fdcadc8fc9eec8a Mon Sep 17 00:00:00 2001 From: stone Date: Thu, 6 Feb 2025 17:06:31 +0800 Subject: [PATCH 02/16] feat: implement the default export function --- .../components/DataStory/controls/defaultExport.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 packages/ui/src/components/DataStory/controls/defaultExport.ts diff --git a/packages/ui/src/components/DataStory/controls/defaultExport.ts b/packages/ui/src/components/DataStory/controls/defaultExport.ts new file mode 100644 index 00000000..9658ed03 --- /dev/null +++ b/packages/ui/src/components/DataStory/controls/defaultExport.ts @@ -0,0 +1,14 @@ +import { Diagram } from '@data-story/core'; + +export const defaultExport = (diagram: Diagram) =>{ + const json = JSON.stringify(diagram, null, 2); + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `diagram-${new Date().toISOString().slice(0,10)}.diagram.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); +} \ No newline at end of file From b0f9b89dded5173126ac9af70a32657e2e22519c Mon Sep 17 00:00:00 2001 From: stone Date: Thu, 6 Feb 2025 17:48:43 +0800 Subject: [PATCH 03/16] refactor: move dataStoryControls to controls directory and extract getOnNodesDelete method --- .../components/DataStory/DataStoryCanvas.tsx | 87 +++++++++++-------- .../{ => controls}/dataStoryControls.tsx | 35 ++++++-- packages/ui/src/index.ts | 4 +- 3 files changed, 84 insertions(+), 42 deletions(-) rename packages/ui/src/components/DataStory/{ => controls}/dataStoryControls.tsx (79%) diff --git a/packages/ui/src/components/DataStory/DataStoryCanvas.tsx b/packages/ui/src/components/DataStory/DataStoryCanvas.tsx index 9a36d35d..e44554a9 100644 --- a/packages/ui/src/components/DataStory/DataStoryCanvas.tsx +++ b/packages/ui/src/components/DataStory/DataStoryCanvas.tsx @@ -1,4 +1,4 @@ -import { DataStoryControls } from './dataStoryControls'; +import { DataStoryControls } from './controls/dataStoryControls'; import React, { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'; import { Background, @@ -27,6 +27,9 @@ import { keyManager } from './keyManager'; import { getNodesWithNewSelection } from './getNodesWithNewSelection'; import { createDataStoryId, LinkCount, LinkId, NodeStatus, RequestObserverType } from '@data-story/core'; import { useDragNode } from './useDragNode'; +import { ReactFlowNode } from '../Node/ReactFlowNode'; +import { defaultExport } from './controls/defaultExport'; +import { defaultImport } from './controls/defaultImport'; const nodeTypes = { commentNodeComponent: CommentNodeComponent, @@ -190,6 +193,51 @@ const Flow = ({ edges, }); + const getOnNodesDelete = useCallback((nodesToDelete: ReactFlowNode[]) => { + nodesToDelete.forEach(node => { + const store = reactFlowStore.getState(); + const { edges } = store; + + // Find all incoming and outgoing edges for this node + const incomingEdges = edges.filter(e => e.target === node.id); + const outgoingEdges = edges.filter(e => e.source === node.id); + + // For each incoming edge, connect it to all outgoing edges + incomingEdges.forEach(inEdge => { + outgoingEdges.forEach(outEdge => { + // Create a connection that will be handled by the store's connect method + connect({ + source: inEdge.source, + sourceHandle: inEdge.sourceHandle ?? null, + target: outEdge.target, + targetHandle: outEdge.targetHandle ?? null, + }); + }); + }); + }); + + // focus on the diagram after node deletion to enhance hotkey usage + focusOnFlow(); + }, [connect, focusOnFlow, reactFlowStore]); + + const onImport = async () => { + console.log('import'); + const diagram = await defaultImport(); + console.log('diagram', diagram); + } + + const onExport = () => { + const diagram = toDiagram(); + + // If client has custom export implementation + // if (client?.onExport) { + // client.onExport(diagram); + // return; + // } + + defaultExport(diagram); + } + return ( <>