diff --git a/easyflow-ui-admin/packages/effects/layouts/src/basic/tabbar/use-tabbar.ts b/easyflow-ui-admin/packages/effects/layouts/src/basic/tabbar/use-tabbar.ts index 897ad99c..29e21318 100644 --- a/easyflow-ui-admin/packages/effects/layouts/src/basic/tabbar/use-tabbar.ts +++ b/easyflow-ui-admin/packages/effects/layouts/src/basic/tabbar/use-tabbar.ts @@ -74,7 +74,11 @@ export function useTabbar() { // 点击tab,跳转路由 const handleClick = (key: string) => { - const { fullPath, path } = tabbarStore.getTabByKey(key); + const tab = tabbarStore.getTabByKey(key); + if (!tab) { + return; + } + const { fullPath, path } = tab; router.push(fullPath || path); }; diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/Tinyflow.ts b/easyflow-ui-admin/packages/tinyflow-ui/src/Tinyflow.ts index 2e9ffff1..d796343c 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/Tinyflow.ts +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/Tinyflow.ts @@ -2,6 +2,10 @@ import type {useSvelteFlow} from '@xyflow/svelte'; import {componentName} from './consts'; import {store} from './store/stores.svelte'; import type {TinyflowData, TinyflowOptions, TinyflowTheme} from './types'; +import {installTinyflowBrowserCompat} from './utils/compat'; +import {createTinyflowNodeNormalizer} from './utils/nodeInteraction'; + +installTinyflowBrowserCompat(); type FlowInstance = ReturnType; @@ -102,6 +106,7 @@ export class Tinyflow { const currentViewport = flow.getViewport(); const currentNodes = flow.getNodes(); + const normalizeNode = createTinyflowNodeNormalizer(this.options); const currentNodePositions = new Map( currentNodes.map((node) => [node.id, node.position]), ); @@ -109,11 +114,12 @@ export class Tinyflow { options?.preserveViewport === true ? (data.nodes || currentNodes).map((node) => { const currentPosition = currentNodePositions.get(node.id); - return currentPosition + const nextNode = currentPosition ? { ...node, position: { ...currentPosition } } : node; + return normalizeNode(nextNode); }) - : data.nodes || currentNodes; + : (data.nodes || currentNodes).map((node) => normalizeNode(node)); store.setNodes(nextNodes); store.setEdges(data.edges || flow.getEdges()); diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/TinyflowComponent.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/TinyflowComponent.svelte index 7178019a..885c6959 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/components/TinyflowComponent.svelte +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/TinyflowComponent.svelte @@ -13,6 +13,7 @@ import {store} from '#store/stores.svelte'; import type {TinyflowData, TinyflowOptions} from '#types'; import {setContext} from 'svelte'; + import {createTinyflowNodeNormalizer} from '../utils/nodeInteraction'; const props = $props<{ options: TinyflowOptions, @@ -49,6 +50,7 @@ }) as TinyflowOptions; const data = parseData(getOptions().data); const initialViewport = data?.viewport || null; + store.setNodeNormalizer(createTinyflowNodeNormalizer(getOptions())); store.init( data?.nodes || [], data?.edges || [], diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/collapse.svelte b/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/collapse.svelte index 7c1997e3..5b498de2 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/collapse.svelte +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/components/base/collapse.svelte @@ -34,7 +34,7 @@
{#each items as item, index}
-
handlerOnChange(item)} onkeydown={(event) => { if (event.key === 'Enter' || event.key === ' ') { @@ -112,6 +112,14 @@ background: var(--tf-bg-hover); } + .tf-node-drag-handle { + cursor: grab; + } + + :global(.svelte-flow__node.dragging) .tf-node-drag-handle { + cursor: grabbing; + } + .tf-collapse-item-title-help::after { content: attr(data-help); position: absolute; diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/index.ts b/easyflow-ui-admin/packages/tinyflow-ui/src/index.ts index 33ee6829..f6adaaab 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/index.ts +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/index.ts @@ -1,3 +1,7 @@ +import { installTinyflowBrowserCompat } from './utils/compat'; + +installTinyflowBrowserCompat(); + export * from './types'; export * from './Tinyflow'; export * from './components/TinyflowComponent.svelte'; diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/store/stores.svelte.ts b/easyflow-ui-admin/packages/tinyflow-ui/src/store/stores.svelte.ts index bf9915ad..f4c795fd 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/src/store/stores.svelte.ts +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/store/stores.svelte.ts @@ -1,4 +1,5 @@ import { type Edge, type Node, type Viewport } from '@xyflow/svelte'; +import type { TinyflowNodeNormalizer } from '../utils/nodeInteraction'; const DEFAULT_VIEWPORT: Viewport = { x: 250, y: 100, zoom: 1 }; @@ -6,20 +7,27 @@ const createStore = () => { let nodesInternal = $state.raw([] as Node[]); let edgesInternal = $state.raw([] as Edge[]); let viewport = $state.raw({ ...DEFAULT_VIEWPORT } as Viewport); + let normalizeNode: TinyflowNodeNormalizer = (node) => node; + + const normalizeNodes = (nodes: Node[]) => nodes.map(normalizeNode); return { // nodes: nodesInternal, // edges: edgesInternal, // viewport, + setNodeNormalizer: (normalizer: TinyflowNodeNormalizer) => { + normalizeNode = normalizer; + nodesInternal = normalizeNodes(nodesInternal); + }, init: (nodes: Node[], edges: Edge[], nextViewport?: Viewport | null) => { - nodesInternal = nodes; + nodesInternal = normalizeNodes(nodes); edgesInternal = edges; viewport = nextViewport ? { ...nextViewport } : { ...DEFAULT_VIEWPORT }; }, getNodes: () => nodesInternal, setNodes: (nodes: Node[]) => { - nodesInternal = nodes; + nodesInternal = normalizeNodes(nodes); }, getEdges: () => edgesInternal, setEdges: (edges: Edge[]) => { @@ -32,18 +40,18 @@ const createStore = () => { getNode: (id: string) => nodesInternal.find((node) => node.id === id), addNode: (node: Node) => { - nodesInternal = [...nodesInternal, node]; + nodesInternal = [...nodesInternal, normalizeNode(node)]; }, removeNode: (id: string) => { nodesInternal = nodesInternal.filter((node) => node.id !== id); }, updateNode: (id: string, node: Node) => { nodesInternal = nodesInternal.map((n) => - n.id === id ? { ...n, ...node } : n, + n.id === id ? normalizeNode({ ...n, ...node }) : n, ); }, updateNodes: (update: (nodes: Node[]) => Node[]) => { - nodesInternal = update(nodesInternal); + nodesInternal = normalizeNodes(update(nodesInternal)); }, updateNodeData: (id: string, data: Node['data']) => { nodesInternal = nodesInternal.map((n) => diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/utils/compat.ts b/easyflow-ui-admin/packages/tinyflow-ui/src/utils/compat.ts new file mode 100644 index 00000000..8262dcc8 --- /dev/null +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/utils/compat.ts @@ -0,0 +1,22 @@ +type StructuredCloneFallback = (value: T) => T; + +/** + * Installs browser compatibility shims required by Tinyflow. + */ +export function installTinyflowBrowserCompat() { + if ( + typeof globalThis.structuredClone === 'function' || + typeof JSON === 'undefined' + ) { + return; + } + + const fallback: StructuredCloneFallback = (value) => + value == null ? value : JSON.parse(JSON.stringify(value)); + + Object.defineProperty(globalThis, 'structuredClone', { + configurable: true, + value: fallback, + writable: true, + }); +} diff --git a/easyflow-ui-admin/packages/tinyflow-ui/src/utils/nodeInteraction.ts b/easyflow-ui-admin/packages/tinyflow-ui/src/utils/nodeInteraction.ts new file mode 100644 index 00000000..541fd2ff --- /dev/null +++ b/easyflow-ui-admin/packages/tinyflow-ui/src/utils/nodeInteraction.ts @@ -0,0 +1,59 @@ +import type { Node } from '@xyflow/svelte'; +import type { CustomNode, TinyflowOptions } from '../types'; + +export const DEFAULT_NODE_DRAG_HANDLE = '.tf-node-drag-handle'; + +export type TinyflowNodeNormalizer = (node: Node) => Node; + +function shouldUseDefaultDragHandle( + node: Node, + customNodes?: Record, +) { + if (Object.prototype.hasOwnProperty.call(node, 'dragHandle')) { + return false; + } + const customNode = node.type ? customNodes?.[node.type] : undefined; + return customNode?.presentation !== 'plain'; +} + +export function createTinyflowNodeNormalizer( + options?: TinyflowOptions, +): TinyflowNodeNormalizer { + const readonly = options?.readonly === true; + const nodesDraggable = options?.nodesDraggable ?? !readonly; + const nodesConnectable = options?.nodesConnectable ?? !readonly; + const elementsSelectable = options?.elementsSelectable ?? !readonly; + + return (node) => { + const nextNode = { ...node }; + + if (nodesDraggable) { + if (nextNode.draggable === false) { + delete nextNode.draggable; + } + if (shouldUseDefaultDragHandle(nextNode, options?.customNodes)) { + nextNode.dragHandle = DEFAULT_NODE_DRAG_HANDLE; + } + } else { + nextNode.draggable = false; + } + + if (nodesConnectable) { + if (nextNode.connectable === false) { + delete nextNode.connectable; + } + } else { + nextNode.connectable = false; + } + + if (elementsSelectable) { + if (nextNode.selectable === false) { + delete nextNode.selectable; + } + } else { + nextNode.selectable = false; + } + + return nextNode; + }; +} diff --git a/easyflow-ui-admin/packages/tinyflow-ui/vite.config.ts b/easyflow-ui-admin/packages/tinyflow-ui/vite.config.ts index 15ca8102..fd7c30fb 100644 --- a/easyflow-ui-admin/packages/tinyflow-ui/vite.config.ts +++ b/easyflow-ui-admin/packages/tinyflow-ui/vite.config.ts @@ -11,6 +11,7 @@ export default defineConfig({ emptyOutDir: true, minify: true, sourcemap: true, + target: 'chrome90', lib: { entry: resolve(__dirname, 'src/index.ts'), cssFileName: 'index', diff --git a/easyflow-ui-admin/packages/tinyflow-vue/src/Tinyflow.vue b/easyflow-ui-admin/packages/tinyflow-vue/src/Tinyflow.vue index 6dff054c..5b1a6668 100644 --- a/easyflow-ui-admin/packages/tinyflow-vue/src/Tinyflow.vue +++ b/easyflow-ui-admin/packages/tinyflow-vue/src/Tinyflow.vue @@ -5,9 +5,18 @@