12 | }
13 |
14 | /**
15 | * 边数据接口
16 | */
17 | export interface IEdge {
18 | id: string
19 | type: string
20 | sourceNodeId: string
21 | targetNodeId: string
22 | }
23 |
24 | /**
25 | * 节点和边的数据接口
26 | */
27 | export interface IGraphData {
28 | nodes: INode[]
29 | edges: IEdge[]
30 | }
31 |
32 | /**
33 | * 自定义插件构造函数接收参数
34 | */
35 | export interface IExtension {
36 | lf: LogicFlow
37 | LogicFlow?: LogicFlow
38 | }
39 |
40 | /**
41 | * 自定义节点构造函数接收参数
42 | */
43 | export interface INodeProps {
44 | graphModel: GraphModel
45 | model: BaseNodeModel
46 | overlay: string
47 | }
48 |
--------------------------------------------------------------------------------
/examples/vue3-app/src/components/dom/DomNumber.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
性能测试
5 |
6 |
7 |
8 |
22 |
--------------------------------------------------------------------------------
/examples/vue3-app/src/components/icons/IconSupport.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/vue3-app/src/main.ts:
--------------------------------------------------------------------------------
1 | import './assets/main.css'
2 | import 'element-plus/dist/index.css'
3 | import '@logicflow/core/dist/index.css'
4 |
5 | import { createApp } from 'vue'
6 | import ElementPlus from 'element-plus'
7 | import App from './App.vue'
8 | import router from './router'
9 |
10 | const app = createApp(App)
11 |
12 | app.use(ElementPlus)
13 | app.use(router)
14 |
15 | app.mount('#app')
16 |
--------------------------------------------------------------------------------
/examples/vue3-app/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import type { DefineComponent } from 'vue'
3 | const component: DefineComponent<{}, {}, any>
4 | export default component
5 | }
6 |
7 | declare module 'vue-virtual-scroller'
8 | declare module '@antv/hierarchy'
9 |
--------------------------------------------------------------------------------
/examples/vue3-app/src/utils/math.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 获取随机整数 [min, max],不包含 [excludemMin, excludeMax]
3 | * @param min
4 | * @param max
5 | * @returns
6 | */
7 | export const getRandom = (min: number, max: number, excludemMin?: number, excludeMax?: number) => {
8 | let res = Math.floor(Math.random() * (max - min + 1) + min)
9 |
10 | if (
11 | excludemMin !== undefined &&
12 | excludeMax !== undefined &&
13 | res >= excludemMin &&
14 | res <= excludeMax
15 | ) {
16 | res = getRandom(min, max, excludemMin, excludeMax)
17 | }
18 |
19 | return res
20 | }
21 |
--------------------------------------------------------------------------------
/examples/vue3-app/src/utils/performance.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 获取页面 dom 数量
3 | * @returns
4 | */
5 | export const getTotalDOMNumber = () => document.querySelectorAll('*').length
6 |
7 | export type PerformanceLongTaskEntry = {
8 | type: 'longTask'
9 | eventType: string
10 | startTime: number
11 | duration: number
12 | }
13 |
14 | /**
15 | * 监控长任务事件响应时间:耗费了 50 毫秒或更多时间
16 | */
17 | export const startObservingLongTasks = (callback: any) => {
18 | const observer = new PerformanceObserver((entryList) => {
19 | const entries = entryList.getEntries()
20 | entries.forEach((entry) => {
21 | requestIdleCallback(() => {
22 | callback(entry)
23 | })
24 | })
25 | })
26 | observer.observe({ entryTypes: ['longtask'] })
27 | }
28 |
--------------------------------------------------------------------------------
/examples/vue3-app/src/views/HomeView.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/vue3-app/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx,vue}'],
4 | theme: {
5 | screens: {
6 | sm: { min: '640px', max: '767px' },
7 | // => @media (min-width: 640px and max-width: 767px) { ... }
8 | md: { min: '768px', max: '1023px' },
9 | // => @media (min-width: 768px and max-width: 1023px) { ... }
10 | lg: { min: '1024px', max: '1599px' },
11 | // => @media (min-width: 1024px and max-width: 1599px) { ... }
12 | xl: { min: '1600px', max: '1919px' },
13 | // => @media (min-width: 1600px and max-width: 1919px) { ... }
14 | '2xl': { min: '1920px' }
15 | // => @media (min-width: 1920px) { ... }
16 | },
17 | extend: {}
18 | },
19 | plugins: []
20 | }
21 |
--------------------------------------------------------------------------------
/examples/vue3-app/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.dom.json",
3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
4 | "exclude": ["src/**/__tests__/*"],
5 | "compilerOptions": {
6 | "composite": true,
7 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
8 |
9 | "baseUrl": ".",
10 | "paths": {
11 | "@/*": ["./src/*"]
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/examples/vue3-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "compilerOptions": {
4 | "verbatimModuleSyntax": false,
5 | "noImplicitAny": false
6 | },
7 | "references": [
8 | {
9 | "path": "./tsconfig.node.json"
10 | },
11 | {
12 | "path": "./tsconfig.app.json"
13 | },
14 | {
15 | "path": "./tsconfig.vitest.json"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/examples/vue3-app/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/node20/tsconfig.json",
3 | "include": [
4 | "vite.config.*",
5 | "vitest.config.*",
6 | "cypress.config.*",
7 | "nightwatch.conf.*",
8 | "playwright.config.*"
9 | ],
10 | "compilerOptions": {
11 | "composite": true,
12 | "noEmit": true,
13 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
14 |
15 | "module": "ESNext",
16 | "moduleResolution": "Bundler",
17 | "types": ["node"],
18 | "noImplicitAny": false
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/vue3-app/tsconfig.vitest.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.app.json",
3 | "exclude": [],
4 | "compilerOptions": {
5 | "composite": true,
6 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo",
7 |
8 | "lib": [],
9 | "types": ["node", "jsdom"]
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/examples/vue3-app/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url'
2 |
3 | import { defineConfig } from 'vite'
4 | import vue from '@vitejs/plugin-vue'
5 | // vite-plugin-vue-devtools 会导致所有的vue节点被记录在 全局变量 __VUE_DEVTOOLS_KIT_PLUGIN_BUFFER__ 上,导致内存溢出的bug
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [vue()],
9 | resolve: {
10 | alias: {
11 | '@': fileURLToPath(new URL('./src', import.meta.url))
12 | }
13 | }
14 | })
15 |
--------------------------------------------------------------------------------
/examples/vue3-app/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath } from 'node:url'
2 | import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
3 | import viteConfig from './vite.config'
4 |
5 | export default mergeConfig(
6 | viteConfig,
7 | defineConfig({
8 | test: {
9 | environment: 'jsdom',
10 | exclude: [...configDefaults.exclude, 'e2e/**'],
11 | root: fileURLToPath(new URL('./', import.meta.url))
12 | }
13 | })
14 | )
15 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/README.md:
--------------------------------------------------------------------------------
1 | # LogicFlow-NodeRed
2 |
3 | LogicFlow仿NodeRed风格流程图。
4 |
5 | ## 效果
6 |
7 | 
8 |
9 | ## codesandbox地址
10 |
11 | [https://codesandbox.io/s/logicflow-node-red-vue3-u2c3zk?file=/src/components/FlowChart.vue](https://codesandbox.io/s/logicflow-node-red-vue3-u2c3zk?file=/src/components/FlowChart.vue)
12 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/auto-imports.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by 'unplugin-auto-import'
2 | // We suggest you to commit this file into source control
3 | declare global {}
4 | export {}
5 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/components.d.ts:
--------------------------------------------------------------------------------
1 | // generated by unplugin-vue-components
2 | // We suggest you to commit this file into source control
3 | // Read more: https://github.com/vuejs/vue-next/pull/3399
4 |
5 | declare module 'vue' {
6 | export interface GlobalComponents {
7 | ElButton: (typeof import('element-plus/es'))['ElButton']
8 | ElCollapse: (typeof import('element-plus/es'))['ElCollapse']
9 | ElCollapseItem: (typeof import('element-plus/es'))['ElCollapseItem']
10 | ElInput: (typeof import('element-plus/es'))['ElInput']
11 | FlowChart: (typeof import('./src/components/FlowChart.vue'))['default']
12 | Palette: (typeof import('./src/components/node-red/tools/Palette.vue'))['default']
13 | Setting: (typeof import('./src/components/node-red/tools/Setting.vue'))['default']
14 | Undefined: (typeof import('./src/components/index.vue'))['default']
15 | VueNode: (typeof import('./src/components/node-red/nodes/VueNode.vue'))['default']
16 | }
17 | }
18 |
19 | export {}
20 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | LogicFlow
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "logicflow-node-red-vue3",
3 | "private": true,
4 | "version": "1.0.1",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@logicflow/core": "workspace:*",
13 | "@logicflow/engine": "workspace:*",
14 | "element-plus": "^2.0.4",
15 | "vue": "^3.2.25"
16 | },
17 | "devDependencies": {
18 | "@vitejs/plugin-vue": "^2.2.0",
19 | "unplugin-auto-import": "^0.6.1",
20 | "unplugin-vue-components": "^0.17.21",
21 | "vite": "^2.8.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/examples/vue3-memory-leak/public/favicon.ico
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/public/images/delay.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/public/images/function.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/public/images/start.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/public/images/swap.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/public/images/switch.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/App.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/examples/vue3-memory-leak/src/assets/logo.png
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/components/engine/index.js:
--------------------------------------------------------------------------------
1 | class Engine {
2 | run() {}
3 | initFlow() {}
4 | }
5 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/components/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
12 | 切换
13 |
14 |
15 |
16 |
21 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/components/node-red/FlowLink.js:
--------------------------------------------------------------------------------
1 | import { BezierEdge, BezierEdgeModel } from '@logicflow/core'
2 |
3 | class FlowLinkModel extends BezierEdgeModel {
4 | getEdgeStyle() {
5 | const style = super.getEdgeStyle()
6 | style.strokeWidth = 3
7 | style.stroke = this.isSelected ? '#ff7f0e' : '#999'
8 | return style
9 | }
10 | }
11 | class FlowLink extends BezierEdge {}
12 |
13 | export default {
14 | type: 'flow-link',
15 | view: FlowLink,
16 | model: FlowLinkModel,
17 | }
18 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/components/node-red/nodes/DelayNode.js:
--------------------------------------------------------------------------------
1 | import { h } from '@logicflow/core'
2 | import BaseNode from './BaseNode'
3 |
4 | class DelayNode extends BaseNode.view {
5 | getIcon() {
6 | const { width, height } = this.props.model
7 | return h('image', {
8 | width: 30,
9 | height: 30,
10 | x: -width / 2,
11 | y: -height / 2,
12 | href: 'images/delay.svg',
13 | })
14 | }
15 | }
16 |
17 | class DelayNodeModel extends BaseNode.model {
18 | initNodeData(data) {
19 | super.initNodeData(data)
20 | this.defaultFill = 'rgb(230, 224, 248)'
21 | }
22 | }
23 |
24 | export default {
25 | type: 'delay-node',
26 | model: DelayNodeModel,
27 | view: DelayNode,
28 | }
29 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/components/node-red/nodes/FetchNode.js:
--------------------------------------------------------------------------------
1 | import { h } from '@logicflow/core'
2 | import BaseNode from './BaseNode'
3 |
4 | class FetchNode extends BaseNode.view {
5 | getIcon() {
6 | const { width, height } = this.props.model
7 | return h('image', {
8 | width: 30,
9 | height: 30,
10 | x: -width / 2,
11 | y: -height / 2,
12 | href: 'images/fetch.svg',
13 | })
14 | }
15 | }
16 |
17 | class FetchNodeModel extends BaseNode.model {
18 | initNodeData(data) {
19 | super.initNodeData(data)
20 | this.defaultFill = 'rgb(231, 231, 174)'
21 | }
22 | }
23 |
24 | export default {
25 | type: 'fetch-node',
26 | model: FetchNodeModel,
27 | view: FetchNode,
28 | }
29 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/components/node-red/nodes/FunctionNode.js:
--------------------------------------------------------------------------------
1 | import { h } from '@logicflow/core'
2 | import BaseNode from './BaseNode'
3 |
4 | class FunctionNode extends BaseNode.view {
5 | getIcon() {
6 | const { width, height } = this.props.model
7 | return h('image', {
8 | width: 30,
9 | height: 30,
10 | x: -width / 2,
11 | y: -height / 2,
12 | href: 'images/function.svg',
13 | })
14 | }
15 | }
16 |
17 | class FunctionNodeModel extends BaseNode.model {
18 | initNodeData(data) {
19 | super.initNodeData(data)
20 | this.defaultFill = 'rgb(253, 208, 162)'
21 | }
22 | }
23 |
24 | export default {
25 | type: 'function-node',
26 | model: FunctionNodeModel,
27 | view: FunctionNode,
28 | }
29 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/components/node-red/nodes/SwitchNode.js:
--------------------------------------------------------------------------------
1 | import { h } from '@logicflow/core'
2 | import BaseNode from './BaseNode'
3 |
4 | class SwitchNode extends BaseNode.view {
5 | getIcon() {
6 | const { width, height } = this.props.model
7 | return h('image', {
8 | width: 30,
9 | height: 30,
10 | x: -width / 2,
11 | y: -height / 2,
12 | href: 'images/switch.svg',
13 | })
14 | }
15 | }
16 |
17 | class SwitchNodeModel extends BaseNode.model {
18 | initNodeData(data) {
19 | super.initNodeData(data)
20 | this.defaultFill = 'rgb(226, 217, 110)'
21 | }
22 | }
23 |
24 | export default {
25 | type: 'switch-node',
26 | model: SwitchNodeModel,
27 | view: SwitchNode,
28 | }
29 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/components/node-red/readme.md:
--------------------------------------------------------------------------------
1 | # 说明
2 |
3 | 参考node-red样式,以logicflow插件的方式实现。
4 |
5 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/components/node-red/style.css:
--------------------------------------------------------------------------------
1 | .custom-anchor {
2 | stroke: #999;
3 | stroke-width: 1;
4 | fill: #d9d9d9;
5 | cursor: crosshair;
6 | rx: 3;
7 | ry: 3;
8 | }
9 | .custom-anchor:hover {
10 | fill: #ff7f0e;
11 | stroke: #ff7f0e;
12 | }
13 | .node-red-palette {
14 | width: 150px;
15 | overflow: hidden;
16 | position: absolute;
17 | left: 0px;
18 | top: 0px;
19 | }
20 | .node-red-start {
21 | cursor: pointer;
22 | pointer-events: all;
23 | }
24 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/components/node-red/util.js:
--------------------------------------------------------------------------------
1 | /* 求字符串的字节长度 */
2 | export const getBytesLength = (word) => {
3 | if (!word) {
4 | return 0
5 | }
6 | let totalLength = 0
7 | for (let i = 0; i < word.length; i++) {
8 | const c = word.charCodeAt(i)
9 | if (word.match(/[A-Z]/)) {
10 | totalLength += 1.5
11 | } else if ((c >= 0x0001 && c <= 0x007e) || (c >= 0xff60 && c <= 0xff9f)) {
12 | totalLength += 1
13 | } else {
14 | totalLength += 1.8
15 | }
16 | }
17 | return totalLength
18 | }
19 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | // import ElementPlus from 'element-plus'
3 | import App from './App.vue'
4 | import './style.css'
5 |
6 | const app = createApp(App)
7 |
8 | // app.use(ElementPlus)
9 |
10 | app.mount('#app')
11 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/src/style.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | width: 100%;
6 | height: 100%;
7 | }
8 | #app {
9 | width: 100%;
10 | height: 100%;
11 | }
12 |
--------------------------------------------------------------------------------
/examples/vue3-memory-leak/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import vue from '@vitejs/plugin-vue'
3 | import AutoImport from 'unplugin-auto-import/vite'
4 | import Components from 'unplugin-vue-components/vite'
5 | import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
6 |
7 | // https://vitejs.dev/config/
8 | export default defineConfig({
9 | // 在codesandbox中需要用443来保证开发时不会重复刷新
10 | // server: {
11 | // hmr: {
12 | // port: 443
13 | // }
14 | // },
15 | base: '/demo/dist/logicflow-node-red-vue3/',
16 | plugins: [
17 | vue(),
18 | AutoImport({
19 | resolvers: [ElementPlusResolver()],
20 | }),
21 | Components({
22 | resolvers: [ElementPlusResolver()],
23 | }),
24 | ],
25 | })
26 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "npmClient": "pnpm",
3 | "npmClientArgs": ["--no-package-lock", "--no-ci"],
4 | "version": "independent",
5 | "useWorkspaces": true,
6 | "packages": [
7 | "packages/core",
8 | "packages/engine",
9 | "packages/extension",
10 | "packages/react-node-registry",
11 | "packages/vue-node-registry"
12 | ],
13 | "command": {
14 | "version": {
15 | "conventionalCommits": true
16 | }
17 | },
18 | "publishConfig": {
19 | "access": "public"
20 | },
21 | "ignoreChanges": ["**/*.md"],
22 | "bootstrap": {
23 | "hoist": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/core/__tests__/event/event.test.ts:
--------------------------------------------------------------------------------
1 | import EventEmitter from '../../src/event/eventEmitter';
2 |
3 | describe('event/eventEmitter', () => {
4 | const em = new EventEmitter();
5 | test('event emitter', () => {
6 | const fn = jest.fn();
7 | em.on('test', fn);
8 | em.emit('test', { a: 1 });
9 | expect(fn).toBeCalledWith({ a: 1 });
10 | em.off('test', fn);
11 | em.emit('test', { a: 1 });
12 | expect(fn).toBeCalledTimes(1);
13 |
14 | em.once('test1', fn);
15 | em.emit('test1', { a: 1 });
16 | expect(fn).toBeCalledTimes(2);
17 | em.once('test1', fn);
18 | em.emit('test1', { a: 1 });
19 | const test1Events = em.getEvents().test1;
20 | expect(test1Events).toBeUndefined();
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/packages/core/__tests__/history/history.test.ts:
--------------------------------------------------------------------------------
1 | import History from '../../src/history/History';
2 | import EventEmitter from '../../src/event/eventEmitter';
3 |
4 | describe('history', () => {
5 | const event = new EventEmitter();
6 | const history = new History(event);
7 | expect(history).toBeDefined();
8 | test('add', () => {
9 | history.add(1);
10 | expect(history.undos).toEqual([1]);
11 | expect(history.redos).toEqual([]);
12 | });
13 | test('undo', () => {
14 | history.add(1);
15 | history.add(2);
16 | history.undo();
17 | expect(history.undos).toEqual([]);
18 | expect(history.redos).toEqual([2]);
19 | });
20 | test('redo', () => {
21 | history.add(1);
22 | history.add(2);
23 | history.undo();
24 | history.redo();
25 | expect(history.undos).toEqual([]);
26 | expect(history.redos).toEqual([]);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/packages/core/__tests__/util/geometry.test.ts:
--------------------------------------------------------------------------------
1 | import { snapToGrid, getGridOffset } from '../../src/util';
2 |
3 | describe('util/geometry', () => {
4 | test('snapToGrid', () => {
5 | const point = 2.5;
6 | const grid = 1.5;
7 | expect(snapToGrid(point, grid) - 3 < Number.EPSILON).toBeTruthy();
8 | });
9 | test('getGridOffset', () => {
10 | const distance = 3;
11 | const grid = 1.5;
12 | expect(getGridOffset(distance, grid) - 2 < Number.EPSILON).toBeTruthy();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/packages/core/__tests__/util/graph.test.ts:
--------------------------------------------------------------------------------
1 | import { isPointInArea } from '../../src/util/graph';
2 |
3 | describe('util/graph', () => {
4 | test('if element is in an area, truthy', () => {
5 | const point: [number, number] = [1, 1];
6 | const leftTopPoint: [number, number] = [0, 0];
7 | const rightBottomPoint: [number, number] = [2, 2];
8 | expect(isPointInArea(point, leftTopPoint, rightBottomPoint)).toBeTruthy();
9 | });
10 | test('if element is in an area, falsy', () => {
11 | const point: [number, number] = [1, 1];
12 | const leftTopPoint: [number, number] = [2, 2];
13 | const rightBottomPoint: [number, number] = [4, 4];
14 | expect(isPointInArea(point, leftTopPoint, rightBottomPoint)).toBeFalsy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/packages/core/__tests__/util/sampling.test.ts:
--------------------------------------------------------------------------------
1 | import {
2 | degrees,
3 | getThetaOfVector,
4 | Vector,
5 | } from '../../src/util';
6 |
7 | describe('util/sampling', () => {
8 | test('degrees', () => {
9 | expect(degrees(1)).toBe(57.29577951308232);
10 | });
11 | test('getThetaOfVector', () => {
12 | expect(
13 | getThetaOfVector(new Vector(1, 1)) - 45 < Number.EPSILON,
14 | ).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/packages/core/__tests__/util/zIndex.test.ts:
--------------------------------------------------------------------------------
1 | import { getZIndex, getMinIndex } from '../../src/util/zIndex';
2 |
3 | describe('util/zIndex', () => {
4 | test('getZIndex', () => {
5 | expect(getZIndex()).toBe(1001);
6 | });
7 | test('getMinIndex', () => {
8 | expect(getMinIndex()).toBe(998);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/packages/core/src/common/index.ts:
--------------------------------------------------------------------------------
1 | export * from './drag'
2 | export * from './history'
3 | export * from './keyboard'
4 |
5 | export * from './matrix'
6 | export * from './vector'
7 |
--------------------------------------------------------------------------------
/packages/core/src/index.less:
--------------------------------------------------------------------------------
1 | @import url('./style/index');
2 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | import { observer as mobxObserver } from 'mobx-preact'
2 | import { createElement as h, createRef, Component } from 'preact/compat'
3 | import LogicFlow from './LogicFlow'
4 |
5 | import * as LogicFlowUtil from './util'
6 |
7 | export function observer(props: P) {
8 | return mobxObserver(props as any)
9 | }
10 |
11 | export { LogicFlow, h, createRef, Component, LogicFlowUtil }
12 |
13 | export * from './util'
14 | export * from './tool'
15 | export * from './view'
16 | export * from './model'
17 | export * from './options'
18 | export * from './keyboard'
19 | export * from './constant'
20 | export * from './algorithm'
21 | export * from './event/eventEmitter'
22 | export { ElementState, ModelType, ElementType, EventType } from './constant'
23 |
24 | export { formatAnchorConnectValidateData } from './util/node'
25 |
26 | export default LogicFlow
27 |
--------------------------------------------------------------------------------
/packages/core/src/model/edge/index.ts:
--------------------------------------------------------------------------------
1 | export * from './BaseEdgeModel';
2 | export * from './BezierEdgeModel';
3 | export * from './LineEdgeModel';
4 | export * from './PolylineEdgeModel';
5 |
--------------------------------------------------------------------------------
/packages/core/src/model/index.ts:
--------------------------------------------------------------------------------
1 | export * from './edge';
2 | export * from './node';
3 |
4 | export * from './BaseModel';
5 | export * from './EditConfigModel';
6 | export * from './GraphModel';
7 | export * from './SnaplineModel';
8 | export * from './TransformModel';
9 |
--------------------------------------------------------------------------------
/packages/core/src/model/node/index.ts:
--------------------------------------------------------------------------------
1 | export * from './BaseNodeModel';
2 | export * from './CircleNodeModel';
3 | export * from './DiamondNodeModel';
4 | export * from './EllipseNodeModel';
5 | export * from './PolygonNodeModel';
6 | export * from './RectNodeModel';
7 | export * from './TextNodeModel';
8 | export * from './HtmlNodeModel';
9 |
--------------------------------------------------------------------------------
/packages/core/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'mobx-preact' {
2 | import { ComponentConstructor } from 'preact/compat'
3 | type Component
= ComponentConstructor
4 | export function observer(target: T): T
5 | }
6 |
--------------------------------------------------------------------------------
/packages/core/src/util/animation.ts:
--------------------------------------------------------------------------------
1 | import { cloneDeep, merge } from 'lodash-es'
2 | import { Options } from '../options'
3 |
4 | import AnimationConfig = Options.AnimationConfig
5 |
6 | export const defaultAnimationOffConfig = {
7 | node: false,
8 | edge: false,
9 | }
10 |
11 | export const defaultAnimationOnConfig = {
12 | node: true,
13 | edge: true,
14 | }
15 |
16 | export const setupAnimation = (
17 | config?: boolean | Partial,
18 | ): AnimationConfig => {
19 | if (!config || typeof config === 'boolean') {
20 | return config === true
21 | ? cloneDeep(defaultAnimationOnConfig)
22 | : cloneDeep(defaultAnimationOffConfig)
23 | }
24 |
25 | // 当传入的是对象时,将其与默认关合并
26 | return merge(cloneDeep(defaultAnimationOffConfig), config)
27 | }
28 |
29 | export const updateAnimation = setupAnimation
30 |
--------------------------------------------------------------------------------
/packages/core/src/util/browser.ts:
--------------------------------------------------------------------------------
1 | import { get } from 'lodash-es'
2 |
3 | export const isIe = () =>
4 | get(window, 'navigator.userAgent', '').match(/MSIE|Trident/) !== null
5 |
--------------------------------------------------------------------------------
/packages/core/src/util/compatible.ts:
--------------------------------------------------------------------------------
1 | // import { cloneDeep } from 'lodash-es'
2 | /**
3 | * 对数据实现兼容处理。
4 | *
5 | * Vue 中的 data 会进行 Observe,深拷贝的原始数据对象
6 | */
7 | export function formatData(data: T): T {
8 | try {
9 | // WARNING: cloneDeep虽然也会将Observe对象转换为plain对象,但是不会像JSON.parse那样,会将undefined去掉。
10 | // 会导致后面的pick因为存在pick覆盖默认值的情况。
11 | return JSON.parse(JSON.stringify(data))
12 | } catch {
13 | return data
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/core/src/util/index.ts:
--------------------------------------------------------------------------------
1 | export * from './animation'
2 | export * from './browser'
3 | export * from './compatible'
4 | export * from './drag'
5 | export * from './edge'
6 | export * from './geometry'
7 | export * from './graph'
8 | export * from './matrix'
9 | export * from './mobx'
10 | export * from './node'
11 | export * from './raf'
12 | export * from './resize'
13 | export * from './sampling'
14 | export * from './theme'
15 | export * from './uuid'
16 | export * from './vector'
17 | export * from './zIndex'
18 |
--------------------------------------------------------------------------------
/packages/core/src/util/mobx.ts:
--------------------------------------------------------------------------------
1 | import {
2 | action,
3 | observable,
4 | computed,
5 | toJS,
6 | isObservable,
7 | configure,
8 | reaction,
9 | IReactionDisposer,
10 | } from 'mobx'
11 |
12 | configure({ isolateGlobalState: true })
13 |
14 | export {
15 | action,
16 | observable,
17 | computed,
18 | isObservable,
19 | toJS,
20 | configure,
21 | reaction,
22 | IReactionDisposer,
23 | }
24 |
--------------------------------------------------------------------------------
/packages/core/src/util/raf.ts:
--------------------------------------------------------------------------------
1 | import { createUuid } from './uuid'
2 |
3 | const rafIdMap = new Map()
4 |
5 | export const createRaf = (callback: () => void) => {
6 | const rafId = createUuid()
7 |
8 | function run() {
9 | callback()
10 | const eId = rafIdMap.get(rafId)
11 | if (eId) {
12 | const nId = window.requestAnimationFrame(run)
13 | rafIdMap.set(rafId, nId)
14 | }
15 | }
16 |
17 | const id = window.requestAnimationFrame(run)
18 | rafIdMap.set(rafId, id)
19 | return rafId
20 | }
21 |
22 | export const cancelRaf = (rafId: string) => {
23 | const eId = rafIdMap.get(rafId)
24 | if (eId) {
25 | window.cancelAnimationFrame(eId)
26 | rafIdMap.delete(rafId)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/core/src/util/uuid.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuidV4 } from 'uuid'
2 | import LogicFlow from '../LogicFlow'
3 |
4 | import GraphData = LogicFlow.GraphData
5 |
6 | export const createUuid = (): string => uuidV4()
7 |
8 | /**
9 | * 重新刷新流程图的所有id
10 | */
11 | export const refreshGraphId = (
12 | graphData: GraphData,
13 | prefix = '',
14 | ): GraphData => {
15 | const nodeIdMap = graphData.nodes.reduce((nMap, node) => {
16 | nMap[node.id] = prefix + uuidV4()
17 | node.id = nMap[node.id]
18 | return nMap
19 | }, {})
20 | graphData.edges.forEach((edge) => {
21 | edge.id = prefix + uuidV4()
22 | edge.sourceNodeId = nodeIdMap[edge.sourceNodeId]
23 | edge.targetNodeId = nodeIdMap[edge.targetNodeId]
24 | })
25 | return graphData
26 | }
27 |
--------------------------------------------------------------------------------
/packages/core/src/util/zIndex.ts:
--------------------------------------------------------------------------------
1 | let maxIndex = 1000
2 | let minIndex = 999
3 |
4 | export const getZIndex = () => ++maxIndex
5 |
6 | export const getMinIndex = () => --minIndex
7 |
--------------------------------------------------------------------------------
/packages/core/src/view/behavior/index.ts:
--------------------------------------------------------------------------------
1 | export * from './dnd'
2 | export * from './snapline'
3 |
--------------------------------------------------------------------------------
/packages/core/src/view/behavior/snapline.ts:
--------------------------------------------------------------------------------
1 | import EventEmitter from '../../event/eventEmitter'
2 | import SnaplineModel from '../../model/SnaplineModel'
3 |
4 | export function snapline(
5 | eventCenter: EventEmitter,
6 | snaplineModel: SnaplineModel,
7 | ): void {
8 | // 节点拖动时启动对齐线计算
9 | eventCenter.on('node:mousemove', ({ data }) => {
10 | snaplineModel.setNodeSnapLine(data)
11 | })
12 | // 节点拖动结束时,对齐线消失
13 | eventCenter.on('node:mouseup', () => {
14 | snaplineModel.clearSnapline()
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/packages/core/src/view/edge/index.ts:
--------------------------------------------------------------------------------
1 | export * from './AdjustPoint'
2 | export * from './Arrow'
3 | export * from './BaseEdge'
4 | export * from './BezierEdge'
5 | export * from './LineEdge'
6 | export * from './PolylineEdge'
7 |
--------------------------------------------------------------------------------
/packages/core/src/view/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Anchor'
2 | export * from './Rotate'
3 | export * from './Graph'
4 |
5 | export * from './shape'
6 | export * from './node'
7 | export * from './edge'
8 | export * from './text'
9 |
--------------------------------------------------------------------------------
/packages/core/src/view/node/CircleNode.tsx:
--------------------------------------------------------------------------------
1 | import Circle from '../shape/Circle'
2 | import BaseNode from './BaseNode'
3 | import { GraphModel, CircleNodeModel } from '../../model'
4 |
5 | export type ICircleNodeProps = {
6 | model: CircleNodeModel
7 | graphModel: GraphModel
8 | }
9 |
10 | export class CircleNode<
11 | P extends ICircleNodeProps = ICircleNodeProps,
12 | > extends BaseNode {
13 | getShape() {
14 | const { model } = this.props
15 | const { x, y, r } = model
16 | const style = model.getNodeStyle()
17 | return
18 | }
19 | }
20 |
21 | export default CircleNode
22 |
--------------------------------------------------------------------------------
/packages/core/src/view/node/DiamondNode.tsx:
--------------------------------------------------------------------------------
1 | import BaseNode from './BaseNode'
2 | import Polygon from '../shape/Polygon'
3 | import { GraphModel, DiamondNodeModel } from '../../model'
4 |
5 | export type IDiamondNodeProps = {
6 | model: DiamondNodeModel
7 | graphModel: GraphModel
8 | }
9 |
10 | export class DiamondNode<
11 | P extends IDiamondNodeProps = IDiamondNodeProps,
12 | > extends BaseNode
{
13 | getShape() {
14 | const { model } = this.props
15 | const style = model.getNodeStyle()
16 | return (
17 |
18 |
19 |
20 | )
21 | }
22 | }
23 |
24 | export default DiamondNode
25 |
--------------------------------------------------------------------------------
/packages/core/src/view/node/EllipseNode.tsx:
--------------------------------------------------------------------------------
1 | import BaseNode from './BaseNode'
2 | import Ellipse from '../shape/Ellipse'
3 | import { GraphModel, EllipseNodeModel } from '../../model'
4 |
5 | export type IEllipseNodeProps = {
6 | model: EllipseNodeModel
7 | graphModel: GraphModel
8 | }
9 |
10 | export class EllipseNode<
11 | P extends IEllipseNodeProps = IEllipseNodeProps,
12 | > extends BaseNode
{
13 | getShape() {
14 | const { model } = this.props
15 | const style = model.getNodeStyle()
16 | return (
17 |
18 | )
19 | }
20 | }
21 |
22 | export default EllipseNode
23 |
--------------------------------------------------------------------------------
/packages/core/src/view/node/PolygonNode.tsx:
--------------------------------------------------------------------------------
1 | import BaseNode from './BaseNode'
2 | import { Polygon } from '../shape'
3 | import { GraphModel, PolygonNodeModel } from '../../model'
4 |
5 | export type IPolygonNodeProps = {
6 | model: PolygonNodeModel
7 | graphModel: GraphModel
8 | }
9 |
10 | export class PolygonNode<
11 | P extends IPolygonNodeProps = IPolygonNodeProps,
12 | > extends BaseNode
{
13 | getShape() {
14 | const { model } = this.props
15 | const { x, y, width, height, points } = model as PolygonNodeModel
16 | const style = model.getNodeStyle()
17 | const attr = {
18 | transform: `matrix(1 0 0 1 ${x - width / 2} ${y - height / 2})`,
19 | }
20 | return (
21 |
22 |
23 |
24 | )
25 | }
26 | }
27 |
28 | export default PolygonNode
29 |
--------------------------------------------------------------------------------
/packages/core/src/view/node/RectNode.tsx:
--------------------------------------------------------------------------------
1 | import { createElement as h } from 'preact/compat'
2 | import BaseNode from './BaseNode'
3 | import { Rect } from '../shape'
4 | import { GraphModel, RectNodeModel } from '../../model'
5 |
6 | export type IRectNodeProps = {
7 | model: RectNodeModel
8 | graphModel: GraphModel
9 | }
10 |
11 | export class RectNode<
12 | P extends IRectNodeProps = IRectNodeProps,
13 | > extends BaseNode
{
14 | getShape(): h.JSX.Element | null {
15 | const { model } = this.props
16 | const style = model.getNodeStyle()
17 | return (
18 |
26 | )
27 | }
28 | }
29 |
30 | export default RectNode
31 |
--------------------------------------------------------------------------------
/packages/core/src/view/node/TextNode.tsx:
--------------------------------------------------------------------------------
1 | import Rect from '../shape/Rect'
2 | import BaseNode from './BaseNode'
3 | import { GraphModel, TextNodeModel } from '../../model'
4 |
5 | export type ITextNodeProps = {
6 | model: TextNodeModel
7 | graphModel: GraphModel
8 | }
9 |
10 | export class TextNode<
11 | P extends ITextNodeProps = ITextNodeProps,
12 | > extends BaseNode
{
13 | getBackground() {
14 | const { model } = this.props
15 | const style = model.getTextStyle()
16 | // 背景框宽度,最长一行字节数/2 * fontsize + 2
17 | // 背景框宽度, 行数 * fontsize + 2
18 | // FIX: #1067
19 | const { width, height, x, y } = model
20 | const rectAttr = {
21 | ...style.background,
22 | x,
23 | y,
24 | width,
25 | height,
26 | }
27 | return
28 | }
29 |
30 | getResizeControl() {
31 | return null
32 | }
33 |
34 | getShape() {
35 | return {this.getBackground()}
36 | }
37 | }
38 |
39 | export default TextNode
40 |
--------------------------------------------------------------------------------
/packages/core/src/view/node/index.ts:
--------------------------------------------------------------------------------
1 | export * from './BaseNode'
2 | export * from './RectNode'
3 | export * from './CircleNode'
4 | export * from './PolygonNode'
5 | export * from './DiamondNode'
6 | export * from './EllipseNode'
7 | export * from './TextNode'
8 | export * from './HtmlNode'
9 |
--------------------------------------------------------------------------------
/packages/core/src/view/overlay/BackgroundOverlay.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from 'preact/compat'
2 | import { isObject } from 'lodash-es'
3 | import { Options as LFOptions } from '../../options'
4 | import { observer } from '../..'
5 |
6 | /**
7 | * 背景配置, 支持css属性配置
8 | * https://developer.mozilla.org/zh-CN/docs/Web/CSS/background
9 | * @example
10 | * {
11 | * backgroundImage: "url('./img/grid.svg')",
12 | backgroundRepeat: 'repeat',
13 | * }
14 | */
15 | type IProps = {
16 | background: boolean | LFOptions.BackgroundConfig
17 | }
18 |
19 | @observer
20 | export class BackgroundOverlay extends Component {
21 | render() {
22 | const { background } = this.props
23 | return (
24 |
30 | )
31 | }
32 | }
33 |
34 | export default BackgroundOverlay
35 |
--------------------------------------------------------------------------------
/packages/core/src/view/overlay/ModificationOverlay.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from 'preact/compat'
2 | import { observer } from '../..'
3 | import GraphModel from '../../model/GraphModel'
4 |
5 | type IProps = {
6 | graphModel: GraphModel
7 | }
8 |
9 | @observer
10 | export class ModificationOverlay extends Component {
11 | render() {
12 | const {
13 | graphModel: { transformModel },
14 | } = this.props
15 | const { transform } = transformModel.getTransformStyle()
16 | const { children } = this.props
17 | return (
18 |
27 | )
28 | }
29 | }
30 |
31 | export default ModificationOverlay
32 |
--------------------------------------------------------------------------------
/packages/core/src/view/overlay/index.ts:
--------------------------------------------------------------------------------
1 | export * from './CanvasOverlay'
2 | export * from './BezierAdjustOverlay'
3 | export * from './BackgroundOverlay'
4 | export * from './Grid'
5 | export * from './ModificationOverlay'
6 | export * from './OutlineOverlay'
7 | export * from './SnaplineOverlay'
8 | export * from './ToolOverlay'
9 |
--------------------------------------------------------------------------------
/packages/core/src/view/shape/Line.tsx:
--------------------------------------------------------------------------------
1 | import { createElement as h } from 'preact/compat'
2 | import { forEach, toPairs } from 'lodash-es'
3 |
4 | export type ILineProps = {
5 | id?: string
6 | tabindex?: number
7 | x1?: number
8 | y1?: number
9 | x2?: number
10 | y2?: number
11 | stroke?: string // Color
12 | className?: string
13 | style?: h.JSX.CSSProperties
14 | [key: string]: any
15 | }
16 |
17 | export function Line(props: ILineProps): h.JSX.Element {
18 | const attrs: ILineProps = {
19 | // default
20 | x1: 10,
21 | y1: 10,
22 | x2: 20,
23 | y2: 20,
24 | stroke: 'black',
25 | // ...props
26 | }
27 |
28 | forEach(toPairs(props), ([k, v]: [k: string, v: any]) => {
29 | if (k === 'style') {
30 | attrs[k] = v
31 | } else if (typeof v !== 'object') {
32 | attrs[k] = v
33 | }
34 | })
35 |
36 | return
37 | }
38 |
39 | export default Line
40 |
--------------------------------------------------------------------------------
/packages/core/src/view/shape/Path.tsx:
--------------------------------------------------------------------------------
1 | import { createElement as h } from 'preact/compat'
2 | import { forEach, toPairs } from 'lodash-es'
3 |
4 | export type IPathProps = {
5 | d: string
6 | [key: string]: any
7 | }
8 |
9 | export function Path(props: IPathProps): h.JSX.Element {
10 | const attrs: Record = {
11 | d: '',
12 | }
13 | forEach(toPairs(props), ([k, v]: [key: string, v: any]) => {
14 | if (k === 'style' || typeof v !== 'object') {
15 | attrs[k] = v
16 | }
17 | })
18 |
19 | return
20 | }
21 |
22 | export default Path
23 |
--------------------------------------------------------------------------------
/packages/core/src/view/shape/Polyline.tsx:
--------------------------------------------------------------------------------
1 | import { createElement as h } from 'preact/compat'
2 | import { forEach, toPairs } from 'lodash-es'
3 |
4 | export type IPolylineProps = {
5 | points: string
6 | pathLength?: number | 'none'
7 | className?: string
8 | }
9 |
10 | export function Polyline(props: IPolylineProps): h.JSX.Element {
11 | const { className } = props
12 | const attrs: Record = {
13 | points: '',
14 | fill: 'none',
15 | }
16 |
17 | forEach(toPairs(props), ([k, v]: [k: string, v: unknown]) => {
18 | if (k === 'style') {
19 | attrs[k] = v
20 | } else if (typeof v !== 'object') {
21 | attrs[k] = v
22 | }
23 | })
24 | if (className) {
25 | attrs.className = `${className}`
26 | }
27 |
28 | return
29 | }
30 |
31 | export default Polyline
32 |
--------------------------------------------------------------------------------
/packages/core/src/view/shape/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Text'
2 | export * from './Line'
3 | export * from './Rect'
4 | export * from './Path'
5 | export * from './Circle'
6 | export * from './Ellipse'
7 | export * from './Polygon'
8 | export * from './Polyline'
9 |
--------------------------------------------------------------------------------
/packages/core/src/view/text/index.ts:
--------------------------------------------------------------------------------
1 | export * from './BaseText'
2 | export * from './LineText'
3 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "allowImportingTsExtensions": false,
5 | "experimentalDecorators": true,
6 | "outDir": "es",
7 | "skipLibCheck": true,
8 | "downlevelIteration": true,
9 | "noImplicitAny": false,
10 | "baseUrl": "./",
11 | "paths": {
12 | "react": ["./node_modules/preact/compat/"],
13 | "react-dom": ["./node_modules/preact/compat/"]
14 | }
15 | },
16 | "include": ["src/**/*", "**/*.d.ts"],
17 | "exclude": ["node_modules", "**/*.spec.ts", "es", "lib", "__tests__"]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/engine/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../babel.config.json"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/engine/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @logicflow/engine
2 |
3 | ## 0.1.1
4 |
5 | ### Patch Changes
6 |
7 | - 更新依赖
8 |
9 | ## 0.1.0
10 |
11 | ### Minor Changes
12 |
13 | - Release 0.1.0 New Version 🎉🎉🎉🎉
14 |
15 | - refactor: 重构 engine 模块代码,使用 sandbox.js 解决 iframe 频繁 append 导致的性能问题
16 | - @logicflow/engine 默认使用 browser 执行代码,node 端也使用 @nyariv/sandboxjs 执行代码片段,保持两端一致
17 |
--------------------------------------------------------------------------------
/packages/engine/README.md:
--------------------------------------------------------------------------------
1 | # @logicflow/engine
2 |
3 | 一个可以在JavaScript环境执行的流程引擎
4 |
5 | ## 使用方式
6 |
7 | ```shell
8 | pnpm run test
9 | ```
10 |
11 | ## 参考资料
12 | 
13 | 
14 |
--------------------------------------------------------------------------------
/packages/engine/rollup.config.js:
--------------------------------------------------------------------------------
1 | import { rollupConfig } from '../../rollup.config'
2 |
3 | export default rollupConfig()
4 |
--------------------------------------------------------------------------------
/packages/engine/src/constant/index.ts:
--------------------------------------------------------------------------------
1 | export * from './LogCode'
2 |
3 | // baseType
4 | export const BASE_START_NODE = 'start'
5 |
6 | // eventType
7 | export const EVENT_INSTANCE_COMPLETE = 'instance:complete'
8 | export const EVENT_INSTANCE_INTERRUPTED = 'instance:interrupted'
9 | export const EVENT_INSTANCE_ERROR = 'instance:error'
10 |
11 | // flowStatus
12 | export enum FlowStatus {
13 | COMPLETED = 'completed',
14 | INTERRUPTED = 'interrupted',
15 | RUNNING = 'running',
16 | PENDING = 'pending',
17 | ERROR = 'error',
18 | }
19 |
20 | // actionStatus
21 | export enum ActionStatus {
22 | SUCCESS = 'success',
23 | ERROR = 'error',
24 | INTERRUPTED = 'interrupted',
25 | }
26 |
--------------------------------------------------------------------------------
/packages/engine/src/nodes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './base'
2 | export * from './start'
3 | export * from './task'
4 |
--------------------------------------------------------------------------------
/packages/engine/src/nodes/start.ts:
--------------------------------------------------------------------------------
1 | import BaseNode from './base'
2 |
3 | export default class StartNode extends BaseNode {
4 | readonly baseType = 'start'
5 | static nodeTypeName = 'StartNode'
6 | }
7 |
8 | export { StartNode }
9 |
--------------------------------------------------------------------------------
/packages/engine/src/nodes/task.ts:
--------------------------------------------------------------------------------
1 | import BaseNode from './base'
2 |
3 | export default class TaskNode extends BaseNode {
4 | readonly baseType = 'task'
5 | static nodeTypeName = 'TaskNode'
6 | }
7 |
8 | export { TaskNode }
9 |
--------------------------------------------------------------------------------
/packages/engine/src/platform/browser/index.ts:
--------------------------------------------------------------------------------
1 | import { runInBrowserContext } from './browserVm'
2 |
3 | const isInBrowser = typeof window === 'object' && window.window === window
4 |
5 | const globalScope: any = (() => {
6 | if (isInBrowser) {
7 | return window
8 | }
9 |
10 | if (typeof self === 'object' && self.self === self) {
11 | return self
12 | }
13 |
14 | if (typeof globalThis === 'object') {
15 | return globalThis
16 | }
17 |
18 | return {
19 | eval: () => undefined,
20 | } as Record
21 | })()
22 |
23 | const getExpressionResult = async (code: string, context: any) => {
24 | const r = await runInBrowserContext(code, context)
25 | return r
26 | }
27 |
28 | export { isInBrowser, globalScope, getExpressionResult }
29 |
--------------------------------------------------------------------------------
/packages/engine/src/platform/index.ts:
--------------------------------------------------------------------------------
1 | export * from './browser'
2 |
--------------------------------------------------------------------------------
/packages/engine/src/platform/node/index.ts:
--------------------------------------------------------------------------------
1 | import { runInNodeContext } from './nodeVm'
2 |
3 | const isInNodeJS = typeof global === 'object' && global.global === global
4 |
5 | const globalScope: any = (() => {
6 | if (typeof self === 'object' && self.self === self) {
7 | return self
8 | }
9 |
10 | if (isInNodeJS) {
11 | return global
12 | }
13 |
14 | if (typeof globalThis === 'object') {
15 | return globalThis
16 | }
17 |
18 | return {
19 | eval: () => undefined,
20 | } as Record
21 | })()
22 |
23 | const getExpressionResult = async (code: string, context: any) => {
24 | const r = await runInNodeContext(code, context)
25 | return r
26 | }
27 |
28 | export { isInNodeJS, globalScope, getExpressionResult }
29 |
--------------------------------------------------------------------------------
/packages/engine/src/platform/node/nodeVm.ts:
--------------------------------------------------------------------------------
1 | import vm from 'node:vm'
2 | // const vm = require('node:vm');
3 |
4 | export const runInNodeContext = async (
5 | code: string,
6 | globalData: Record = {},
7 | ): Promise => {
8 | const context = vm.createContext(globalData)
9 | vm.runInContext(code, context)
10 |
11 | console.log('context ===>>>', context)
12 |
13 | return context
14 | }
15 |
--------------------------------------------------------------------------------
/packages/engine/src/utils/global.ts:
--------------------------------------------------------------------------------
1 | // 判断当前环境是否为服务端
2 | // const isServer = typeof window === undefined;
3 |
4 | // const isServer = process.env.BROWSER === true;
5 |
6 | const isInBrowser = typeof window === 'object' && window.window === window
7 |
8 | const isInNodeJS = typeof global === 'object' && global.global === global
9 |
10 | const isInWebWorker =
11 | !isInBrowser && typeof self === 'object' && self.constructor
12 |
13 | const globalScope: any = (() => {
14 | if (isInBrowser) {
15 | return window
16 | }
17 |
18 | if (typeof self === 'object' && self.self === self) {
19 | return self
20 | }
21 |
22 | if (isInNodeJS) {
23 | return global
24 | }
25 |
26 | if (typeof globalThis === 'object') {
27 | return globalThis
28 | }
29 |
30 | return {
31 | eval: () => undefined,
32 | } as Record
33 | })()
34 |
35 | export {
36 | // 环境相关方法
37 | globalScope,
38 | isInWebWorker,
39 | isInBrowser,
40 | isInNodeJS,
41 | }
42 |
--------------------------------------------------------------------------------
/packages/engine/src/utils/id.ts:
--------------------------------------------------------------------------------
1 | import { v4 as uuidV4 } from 'uuid'
2 |
3 | export const createExecId = (): string => {
4 | const uuid = uuidV4()
5 | return `exec-${uuid}`
6 | }
7 |
8 | export const createActionId = (): string => {
9 | const uuid = uuidV4()
10 | return `action-${uuid}`
11 | }
12 |
13 | export const createEngineId = (): string => {
14 | const uuid = uuidV4()
15 | return `engine-${uuid}`
16 | }
17 |
--------------------------------------------------------------------------------
/packages/engine/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import storage from './storage'
2 |
3 | export * from './global'
4 | export * from './id'
5 | export { storage }
6 |
--------------------------------------------------------------------------------
/packages/engine/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2016",
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "declaration": true,
7 | "sourceMap": true,
8 | "esModuleInterop": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "strict": true,
11 | "noImplicitAny": false,
12 | "skipLibCheck": true,
13 | "importHelpers": true
14 | },
15 | "include": ["src/**/*"],
16 | "exclude": ["node_modules", "examples", "**/*.spec.ts", "**/*.d.ts"]
17 | }
18 |
--------------------------------------------------------------------------------
/packages/extension/README.md:
--------------------------------------------------------------------------------
1 | # extension
2 |
3 | LogicFlow 扩展包
4 |
5 | ## 使用方式
6 |
7 | ```js
8 | import LogicFlow from '@logicflow/core';
9 | import { BpmnAdapter } from '@logicflow/extension';
10 |
11 | LogicFlow.use(BpmnAdapter);
12 | ```
13 |
--------------------------------------------------------------------------------
/packages/extension/src/NodeResize/BasicShape/Ellipse.tsx:
--------------------------------------------------------------------------------
1 | import { h } from '@logicflow/core'
2 |
3 | function Ellipse(props: Record): h.JSX.Element {
4 | const { x = 0, y = 0, rx = 4, ry = 4 } = props
5 |
6 | const attrs = {
7 | cx: x,
8 | cy: y,
9 | rx,
10 | ry,
11 | fill: 'transparent',
12 | fillOpacity: 1,
13 | strokeWidth: 1,
14 | stroke: '#000',
15 | strokeOpacity: 1,
16 | ...props,
17 | }
18 |
19 | return
20 | }
21 |
22 | export default Ellipse
23 |
--------------------------------------------------------------------------------
/packages/extension/src/NodeResize/BasicShape/Polygon.tsx:
--------------------------------------------------------------------------------
1 | import { h } from '@logicflow/core'
2 |
3 | export default function Polygon({
4 | fillOpacity = 1,
5 | strokeWidth = 1,
6 | strokeOpacity = 1,
7 | fill = 'transparent',
8 | stroke = '#000',
9 | points,
10 | className = 'lf-basic-shape',
11 | }: any): h.JSX.Element {
12 | const attrs = {
13 | fill,
14 | fillOpacity,
15 | strokeWidth,
16 | stroke,
17 | strokeOpacity,
18 | points: '',
19 | className,
20 | }
21 | attrs.points = points.map((point: any) => point.join(',')).join(' ')
22 |
23 | return
24 | }
25 |
--------------------------------------------------------------------------------
/packages/extension/src/NodeResize/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @deprecated
3 | * 待废弃,2.0 版本将 NodeResize 能力内置,该插件设计和实现有比较多的问题,后续不再维护,请及时切换
4 | */
5 | import LogicFlow from '@logicflow/core'
6 |
7 | import EllipseResize from './node/EllipseResize'
8 | import DiamondResize from './node/DiamondResize'
9 | import HtmlResize from './node/HtmlResize'
10 | import RectResize from './node/RectResize'
11 |
12 | export * from './node'
13 | export const NodeResize = {
14 | pluginName: 'nodeResize',
15 | // 拖动step
16 | step: 0,
17 |
18 | install(lf: LogicFlow) {
19 | lf.register(EllipseResize)
20 | lf.register(DiamondResize)
21 | lf.register(HtmlResize)
22 | lf.register(RectResize)
23 | },
24 | }
25 |
26 | export default NodeResize
27 |
--------------------------------------------------------------------------------
/packages/extension/src/NodeResize/node/index.ts:
--------------------------------------------------------------------------------
1 | export * from './DiamondResize'
2 | export * from './EllipseResize'
3 | export * from './HtmlResize'
4 | export * from './RectResize'
5 |
--------------------------------------------------------------------------------
/packages/extension/src/bpmn-adapter/bpmnIds.ts:
--------------------------------------------------------------------------------
1 | export class IDs {
2 | private _ids: Set
3 |
4 | constructor() {
5 | globalThis._ids = this
6 | this._ids = new Set()
7 | }
8 |
9 | generateId() {
10 | return 'xxxxxxx'.replace(/[x]/g, (c) => {
11 | const r = (Math.random() * 16) | 0
12 | const v = c === 'x' ? r : (r & 0x3) | 0x8
13 | return v.toString(16)
14 | })
15 | }
16 |
17 | next() {
18 | let id = this.generateId()
19 | while (this._ids.has(id)) {
20 | id = this.generateId()
21 | }
22 | this._ids.add(id)
23 | return id
24 | }
25 | }
26 |
27 | const ids = globalThis?._ids || new IDs()
28 |
29 | export function getBpmnId(): string {
30 | return ids.next()
31 | }
32 |
--------------------------------------------------------------------------------
/packages/extension/src/bpmn-elements/index.d.ts:
--------------------------------------------------------------------------------
1 | type DefinitionConfigType = {
2 | nodes: string[];
3 | definition: EventDefinitionType[] | TaskDefinitionType[];
4 | };
5 |
6 | type DefinitionPropertiesType = {
7 | definitionType: string;
8 | timerType?: TimerType;
9 | timerValue?: string;
10 | [key: string]: any;
11 | };
12 |
13 | type EventDefinitionType = {
14 | type: string;
15 | icon: string | Object;
16 | toJSON: Function;
17 | properties: DefinitionPropertiesType;
18 | [key: string]: any;
19 | };
20 |
21 | type TaskDefinitionType = {
22 | type: string;
23 | [key: string]: any;
24 | };
25 |
26 | type TimerType = 'timerCycle' | 'timerDate' | 'timerDuration';
27 |
--------------------------------------------------------------------------------
/packages/extension/src/bpmn-elements/presets/Event/index.ts:
--------------------------------------------------------------------------------
1 | import LogicFlow from '@logicflow/core'
2 | import { EndEventFactory } from './EndEventFactory'
3 | import { IntermediateCatchEventFactory } from './IntermediateCatchEvent'
4 | import { StartEventFactory } from './StartEventFactory'
5 | import { BoundaryEventFactory } from './boundaryEventFactory'
6 | import { IntermediateThrowEventFactory } from './IntermediateThrowEvent'
7 |
8 | export function registerEventNodes(lf: LogicFlow) {
9 | lf.register(StartEventFactory(lf))
10 | lf.register(EndEventFactory(lf))
11 | lf.register(IntermediateCatchEventFactory(lf))
12 | lf.register(IntermediateThrowEventFactory(lf))
13 | lf.register(BoundaryEventFactory(lf))
14 | }
15 |
--------------------------------------------------------------------------------
/packages/extension/src/bpmn-elements/presets/Flow/flow.d.ts:
--------------------------------------------------------------------------------
1 | export type BBoxType = {
2 | minX: number;
3 | minY: number;
4 | maxX: number;
5 | maxY: number;
6 | }
7 |
--------------------------------------------------------------------------------
/packages/extension/src/bpmn-elements/presets/Flow/index.ts:
--------------------------------------------------------------------------------
1 | import LogicFlow from '@logicflow/core'
2 | import { sequenceFlowFactory } from './sequenceFlow'
3 |
4 | export const SequenceFlow = sequenceFlowFactory()
5 |
6 | export function registerFlows(lf: LogicFlow) {
7 | lf.register(SequenceFlow)
8 | }
9 |
--------------------------------------------------------------------------------
/packages/extension/src/bpmn-elements/presets/Gateway/index.ts:
--------------------------------------------------------------------------------
1 | import LogicFlow from '@logicflow/core'
2 | import { exclusiveIcon, parallelIcon, inclusiveIcon } from '../icons'
3 | import { GatewayNodeFactory } from './gateway'
4 |
5 | export function registerGatewayNodes(lf: LogicFlow) {
6 | const ExclusiveGateway = GatewayNodeFactory(
7 | 'bpmn:exclusiveGateway',
8 | exclusiveIcon,
9 | )
10 |
11 | const ParallelGateway = GatewayNodeFactory(
12 | 'bpmn:parallelGateway',
13 | parallelIcon,
14 | )
15 |
16 | const InclusiveGateway = GatewayNodeFactory(
17 | 'bpmn:inclusiveGateway',
18 | inclusiveIcon,
19 | )
20 | lf.register(ExclusiveGateway)
21 | lf.register(InclusiveGateway)
22 | lf.register(ParallelGateway)
23 | }
24 |
--------------------------------------------------------------------------------
/packages/extension/src/bpmn/events/index.ts:
--------------------------------------------------------------------------------
1 | export * from './StartEvent'
2 | export * from './EndEvent'
3 |
--------------------------------------------------------------------------------
/packages/extension/src/bpmn/flow/SequenceFlow.ts:
--------------------------------------------------------------------------------
1 | import { PolylineEdge, PolylineEdgeModel } from '@logicflow/core'
2 | import { getBpmnId } from '../getBpmnId'
3 |
4 | export class SequenceFlowModel extends PolylineEdgeModel {
5 | static extendKey = 'SequenceFlowModel'
6 |
7 | constructor(data, graphModel) {
8 | if (!data.id) {
9 | data.id = `Flow_${getBpmnId()}`
10 | }
11 | super(data, graphModel)
12 | }
13 | }
14 |
15 | export class SequenceFlowView extends PolylineEdge {
16 | static extendKey = 'SequenceFlowEdge'
17 | }
18 |
19 | export const SequenceFlow = {
20 | type: 'bpmn:sequenceFlow',
21 | view: SequenceFlowView,
22 | model: SequenceFlowModel,
23 | }
24 |
25 | export default SequenceFlow
26 |
--------------------------------------------------------------------------------
/packages/extension/src/bpmn/flow/index.ts:
--------------------------------------------------------------------------------
1 | export * from './SequenceFlow'
2 |
--------------------------------------------------------------------------------
/packages/extension/src/bpmn/gateways/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ExclusiveGateway'
2 |
--------------------------------------------------------------------------------
/packages/extension/src/bpmn/getBpmnId.ts:
--------------------------------------------------------------------------------
1 | class IDS {
2 | private _ids: Set
3 |
4 | constructor() {
5 | globalThis._ids = this
6 | this._ids = new Set()
7 | }
8 |
9 | generateId() {
10 | return 'xxxxxxx'.replace(/[x]/g, (c) => {
11 | const r = (Math.random() * 16) | 0
12 | const v = c === 'x' ? r : (r & 0x3) | 0x8
13 | return v.toString(16)
14 | })
15 | }
16 |
17 | next() {
18 | let id = this.generateId()
19 | while (this._ids.has(id)) {
20 | id = this.generateId()
21 | }
22 | this._ids.add(id)
23 | return id
24 | }
25 | }
26 |
27 | const ids = globalThis?._ids || new IDS()
28 |
29 | export function getBpmnId(): string {
30 | return ids.next()
31 | }
32 |
--------------------------------------------------------------------------------
/packages/extension/src/bpmn/tasks/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ServiceTask'
2 | export * from './UserTask'
3 |
--------------------------------------------------------------------------------
/packages/extension/src/index.less:
--------------------------------------------------------------------------------
1 | @import url('./style/index.less');
2 |
--------------------------------------------------------------------------------
/packages/extension/src/mindmap/fakerRoot.ts:
--------------------------------------------------------------------------------
1 | import { BaseNode, RectNodeModel } from '@logicflow/core'
2 |
3 | class MarkRootModel extends RectNodeModel {
4 | static extendKey = 'MarkRootModel'
5 | }
6 |
7 | class MarkRootView extends BaseNode {
8 | getShape() {
9 | return null
10 | }
11 | }
12 |
13 | const MarkRoot = {
14 | type: 'faker:root',
15 | view: MarkRootView,
16 | model: MarkRootModel,
17 | }
18 |
19 | export default MarkRoot
20 |
--------------------------------------------------------------------------------
/packages/extension/src/mindmap/theme.ts:
--------------------------------------------------------------------------------
1 | export const theme = {
2 | rect: {
3 | // outlineColor: 'transparent'
4 | stroke: '#187DFF',
5 | },
6 | bezier: {
7 | offset: 100,
8 | strokeWidth: 1,
9 | stroke: '#a4a5a6',
10 | },
11 | }
12 |
--------------------------------------------------------------------------------
/packages/extension/src/rect-label-node/RectLabelNodeView.ts:
--------------------------------------------------------------------------------
1 | import { RectNode, h } from '@logicflow/core'
2 |
3 | export class RectLabelNodeView extends RectNode {
4 | getLabelShape(): h.JSX.Element {
5 | const { x, y, width, height, properties } = this.props.model
6 |
7 | return h(
8 | 'text',
9 | {
10 | x: x - width / 2 + 5,
11 | y: y - height / 2 + 16,
12 | fontSize: 12,
13 | fill: 'blue',
14 | },
15 | properties.moreText as string,
16 | )
17 | }
18 |
19 | getShape(): h.JSX.Element {
20 | const { x, y, width, height } = this.props.model
21 | const style = this.props.model.getNodeStyle()
22 | // todo: 将basic-shape对外暴露,在这里可以直接用。现在纯手写有点麻烦。
23 | return h('g', {}, [
24 | h('rect', {
25 | ...style,
26 | fill: '#FFFFFF',
27 | x: x - width / 2,
28 | y: y - height / 2,
29 | }),
30 | this.getLabelShape(),
31 | ])
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/extension/src/rect-label-node/index.ts:
--------------------------------------------------------------------------------
1 | import { RectNodeModel } from '@logicflow/core'
2 | import { RectLabelNodeView } from './RectLabelNodeView'
3 |
4 | export const RectLabelNode = {
5 | pluginName: 'rectLabelNode',
6 | install(lf) {
7 | lf.register({
8 | type: 'rect-label',
9 | model: RectNodeModel,
10 | view: RectLabelNodeView,
11 | })
12 | },
13 | }
14 |
15 | export default RectLabelNode
16 |
--------------------------------------------------------------------------------
/packages/extension/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "allowImportingTsExtensions": false,
5 | "experimentalDecorators": true,
6 | "outDir": "es",
7 | "skipLibCheck": true,
8 | "downlevelIteration": true,
9 | "noImplicitAny": false,
10 | "baseUrl": "./",
11 | "paths": {
12 | "react": ["./node_modules/preact/compat/"],
13 | "react-dom": ["./node_modules/preact/compat/"]
14 | }
15 | },
16 | "include": ["src/**/*", "**/*.d.ts"],
17 | "exclude": ["node_modules", "**/*.spec.ts", "es", "lib"]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/react-node-registry/README.md:
--------------------------------------------------------------------------------
1 | # @logicflow/react-node-registry
2 |
3 | LogicFlow Shape for rendering React components.
4 |
5 | ## Installation
6 | ```shell
7 | npm install @logicflow/react-node-registry
8 | ```
9 | or
10 | ```shell
11 | yarn add @logicflow/react-node-registry
12 | ```
13 | or
14 | ```shell
15 | pnpm add @logicflow/react-node-registry
16 | ```
17 |
18 | ## Usage
19 | 用户自定义 React 节点 CustomReactComp
20 |
21 | 我们需要将该内容注册到某个地方
22 |
23 | 1. 如果基于默认的 HTML 节点,只是调整节点里面的内容,
24 |
25 | ```typescript
26 | register({
27 | type: 'custom-react-shape',
28 | view: ReactComponent,
29 | model: ReactNodeModel,
30 | }, lf)
31 | ```
32 |
33 | 1. 注册节点
34 | 2. `view` 为 React 组件(基于 HTMLNode 自定义)
35 | 3. `model` 为节点数据模型
36 |
--------------------------------------------------------------------------------
/packages/react-node-registry/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './view'
2 | export * from './model'
3 | export * from './registry'
4 | export * from './wrapper'
5 | export * from './portal'
6 |
--------------------------------------------------------------------------------
/packages/react-node-registry/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "moduleResolution": "node",
5 | "sourceMap": true,
6 | "declaration": true,
7 | "skipLibCheck": true,
8 | "esModuleInterop": true,
9 | "noImplicitAny": true,
10 | "noEmitOnError": true,
11 | "noUnusedLocals": true,
12 | "strictNullChecks": true,
13 | "resolveJsonModule": true,
14 | "experimentalDecorators": true,
15 | "jsx": "react",
16 | "target": "es6",
17 | "lib": ["DOM", "ES2020"]
18 | },
19 | "include": ["src/**/*", "**/*.d.ts"],
20 | "exclude": ["node_modules", "**/*.spec.ts", "es", "lib", "dist"]
21 | }
22 |
--------------------------------------------------------------------------------
/packages/vue-node-registry/README.md:
--------------------------------------------------------------------------------
1 | # @logicflow/vue-node-registry
2 |
3 | LogicFlow Shape for rendering Vue components.
4 |
5 | ## Installation
6 | ```shell
7 | npm install @logicflow/vue-node-registry
8 | ```
9 | or
10 | ```shell
11 | yarn add @logicflow/vue-node-registry
12 | ```
13 | or
14 | ```shell
15 | pnpm add @logicflow/vue-node-registry
16 | ```
17 |
18 | ## Usage
19 | 用户自定义 Vue 节点 CustomVueComponent
20 |
21 | 我们需要将该内容注册到某个地方
22 |
23 | 1. 如果基于默认的 HTML 节点,只是调整节点里面的内容,
24 |
25 | ```typescript
26 | register({
27 | type: 'custom-vue-shape',
28 | view: VueComponent,
29 | model: VueNodeModel,
30 | }, lf)
31 | ```
32 |
33 | 1. 注册节点
34 | 2. `view` 为 Vue 组件(基于 HTMLNode 自定义)
35 | 3. `model` 为节点数据模型
36 |
--------------------------------------------------------------------------------
/packages/vue-node-registry/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './view'
2 | export * from './model'
3 | export * from './registry'
4 | export * from './teleport'
5 |
--------------------------------------------------------------------------------
/packages/vue-node-registry/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "exclude": ["node_modules", "**/es", "**/lib"],
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "declaration": true,
8 | "skipLibCheck": true,
9 | "esModuleInterop": true,
10 | "noImplicitAny": true,
11 | "noEmitOnError": true,
12 | "noUnusedLocals": true,
13 | "strictNullChecks": true,
14 | "resolveJsonModule": true,
15 | "experimentalDecorators": true,
16 | "jsx": "react",
17 | "target": "es6",
18 | "lib": ["DOM", "ES2020"]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'packages/**'
3 | - 'examples/**'
4 | - 'sites/**'
5 | - 'scripts'
6 |
--------------------------------------------------------------------------------
/scripts/build-docs:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if ! [ -x "$(command -v pnpm)" ]; then
4 | echo 'Error: pnpm is not installed. install pnpm...' >&2
5 | npm install -g pnpm
6 | fi
7 |
8 | pnpm install
9 | pnpm build
10 |
11 | cd sites/docs
12 | rm -rf ./dist
13 | pnpm build
14 |
--------------------------------------------------------------------------------
/scripts/sync-gitee:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | SOURCE_REPO=git@github.com:didi/LogicFlow.git
6 | DESTINATION_REPO=git@gitee.com:logic-flow/LogicFlow.git
7 | SOURCE_DIR=./tmp
8 | DRY_RUN=false
9 |
10 | GIT_SSH_COMMAND="ssh -v"
11 |
12 | echo "SOURCE=$SOURCE_REPO"
13 | echo "DESTINATION=$DESTINATION_REPO"
14 | echo "DRY RUN=$DRY_RUN"
15 |
16 | git clone --mirror "$SOURCE_REPO" "$SOURCE_DIR" && cd "$SOURCE_DIR"
17 | git remote set-url --push origin "$DESTINATION_REPO"
18 | git fetch -p origin
19 | # Exclude refs created by GitHub for pull request.
20 | git for-each-ref --format 'delete %(refname)' refs/pull | git update-ref --stdin
21 |
22 | if [ "$DRY_RUN" = "true" ]
23 | then
24 | echo "INFO: Dry Run, no data is pushed"
25 | git push --mirror --dry-run
26 | else
27 | git push --mirror
28 | fi
--------------------------------------------------------------------------------
/sites/docs/.dumi/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/.dumi/favicon.png
--------------------------------------------------------------------------------
/sites/docs/.dumi/global.less:
--------------------------------------------------------------------------------
1 | @import url('@logicflow/core/es/index.css');
2 | @import url('@logicflow/extension/es/index.css');
3 |
--------------------------------------------------------------------------------
/sites/docs/.dumi/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "include": ["**/*"]
4 | }
5 |
--------------------------------------------------------------------------------
/sites/docs/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/sites/docs/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | /dist
3 | .dumi/tmp
4 | .dumi/tmp-production
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/sites/docs/.prettierignore:
--------------------------------------------------------------------------------
1 | .dumi/tmp
2 | .dumi/tmp-production
3 | *.yaml
4 |
--------------------------------------------------------------------------------
/sites/docs/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 80,
3 | proseWrap: 'never',
4 | singleQuote: true,
5 | trailingComma: 'all',
6 | overrides: [
7 | {
8 | files: '*',
9 | options: {
10 | proseWrap: 'preserve',
11 | },
12 | },
13 | ],
14 | };
15 |
--------------------------------------------------------------------------------
/sites/docs/README.md:
--------------------------------------------------------------------------------
1 | # @logicflow-sites
2 |
3 | A static site base on [dumi](https://d.umijs.org).
4 |
5 | ## Development
6 |
7 | ```bash
8 | # install dependencies
9 | $ pnpm install
10 |
11 | # start dev server
12 | $ pnpm start
13 |
14 | # build docs
15 | $ pnpm run build
16 | ```
17 |
18 | ## LICENSE
19 |
20 | MIT
21 |
--------------------------------------------------------------------------------
/sites/docs/docs/tutorial/advanced/snapline.zh.md:
--------------------------------------------------------------------------------
1 | ---
2 | nav: 指南
3 | group:
4 | title: 进阶
5 | order: 2
6 | title: 对齐线
7 | order: 2
8 | toc: content
9 | ---
10 |
11 | 对齐线能够在节点移动过程中,将移动节点的位置与画布中其他节点位置进行对比,辅助位置调整。位置对比有如下两个方面。
12 |
13 | - 节点中心位置
14 | - 节点的边框
15 |
16 | ## 对齐线使用
17 |
18 | 普通编辑模式下,默认开启对齐线,也可通过配置进行关闭。
19 | 在[静默模式](silent-mode.zh.md#静默模式)下,无法移动节点,所以关闭了对齐线功能,无法通过配置开启。
20 |
21 | ```tsx | pure
22 | // 关闭对齐线功能
23 | const lf = new LogicFlow({
24 | snapline: false,
25 | })
26 | ```
27 |
28 | ## 对齐线样式设置
29 |
30 | 对齐线的样式包括颜色和宽度,可以通过设置主题的方式进行修改。
31 |
32 | ```tsx | pure
33 | // 默认配置
34 | // {
35 | // stroke: '#1E90FF',
36 | // strokeWidth: 1,
37 | // }
38 |
39 | // 修改对齐线样式
40 | lf.setTheme({
41 | snapline: {
42 | stroke: '#1E90FF', // 对齐线颜色
43 | strokeWidth: 1, // 对齐线宽度
44 | },
45 | })
46 | ```
47 |
48 |
49 |
50 | 更多样式修改参见[主题](../basic/theme.zh.md)
51 |
--------------------------------------------------------------------------------
/sites/docs/examples/edge/custom/demo/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "demos": [
3 | {
4 | "filename": "polyline.tsx",
5 | "title": {
6 | "zh": "自定义折线",
7 | "en": "Custom Polyline"
8 | },
9 | "screenshot": "https://cdn.jsdelivr.net/gh/Logic-Flow/static@latest/docs/examples/edge/custom-polyline.png"
10 | },
11 | {
12 | "filename": "curvedPolyline.tsx",
13 | "title": {
14 | "zh": "圆角折线",
15 | "en": "Curved Polyline"
16 | },
17 | "screenshot": "https://cdn.jsdelivr.net/gh/Logic-Flow/static@latest/docs/examples/edge/rounded-polyline.png"
18 | },
19 | {
20 | "filename": "animatePolyline.tsx",
21 | "title": {
22 | "zh": "动画折线",
23 | "en": "Animate Polyline"
24 | },
25 | "screenshot": "https://cdn.jsdelivr.net/gh/Logic-Flow/static@latest/docs/examples/edge/animate-edge.png"
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/sites/docs/examples/edge/custom/index.en.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Custom Edges
3 | order: 2
4 | redirect_from:
5 | - /zh/examples/edge
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/edge/custom/index.zh.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 自定义边
3 | order: 2
4 | redirect_from:
5 | - /zh/examples/edge
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/edge/native/demo/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "demos": [
3 | {
4 | "filename": "basicEdge.tsx",
5 | "title": {
6 | "zh": "基础内置边",
7 | "en": "Basic Native Edges"
8 | },
9 | "screenshot": "https://cdn.jsdelivr.net/gh/Logic-Flow/static@latest/docs/examples/edge/basic-edge.png"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/sites/docs/examples/edge/native/index.en.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Native Edges
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/native
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/edge/native/index.zh.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 内置边
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/native
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/extension/native/index.en.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Built-in plugins
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/extension
6 | ---
--------------------------------------------------------------------------------
/sites/docs/examples/extension/native/index.zh.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 内置插件
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/extension
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/graph/basic/index.en.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Basic Graph
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/basic
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/graph/basic/index.zh.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 基础画布
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/basic
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/node/custom/index.en.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Custom Nodes
3 | order: 2
4 | redirect_from:
5 | - /zh/examples/custom
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/node/custom/index.zh.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 自定义节点
3 | order: 2
4 | redirect_from:
5 | - /zh/examples/custom
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/node/native/demo/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "demos": [
3 | {
4 | "filename": "basicNode.tsx",
5 | "title": {
6 | "zh": "基础内置节点",
7 | "en": "Basic Native Nodes"
8 | },
9 | "screenshot": "https://cdn.jsdelivr.net/gh/Logic-Flow/static@latest/docs/examples/node/native-node.png"
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/sites/docs/examples/node/native/index.en.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Native Nodes
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/native
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/node/native/index.zh.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 内置节点
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/native
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/react/registry/demo/meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "demos": [
3 | {
4 | "filename": "customReact.tsx",
5 | "title": {
6 | "zh": "自定义 react 节点",
7 | "en": "Custom React Nodes"
8 | },
9 | "screenshot": "https://cdn.jsdelivr.net/gh/Logic-Flow/static@latest/docs/examples/react/react-portal.png"
10 | },
11 | {
12 | "filename": "reactPortal.tsx",
13 | "title": {
14 | "zh": "React Portal 节点",
15 | "en": "React Portal Nodes"
16 | },
17 | "screenshot": "https://cdn.jsdelivr.net/gh/Logic-Flow/static@latest/docs/examples/react/react-portal.png"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/sites/docs/examples/react/registry/index.en.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: React Node Registry
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/registry
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/react/registry/index.zh.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: react 注册节点
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/registry
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/bussiness/demo/draft.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/bussiness/demo/draft.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/bussiness/demo/games.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/bussiness/demo/games.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/bussiness/demo/mvp.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/bussiness/demo/mvp.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/bussiness/demo/organizer.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/bussiness/demo/organizer.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/bussiness/demo/pool.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/bussiness/demo/pool.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/bussiness/demo/profileEditor.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/bussiness/demo/profileEditor.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/bussiness/index.en.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Bussiness Scene
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/bussiness
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/bussiness/index.zh.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 业务场景
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/bussiness
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/example/demo/angularDemo.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/example/demo/angularDemo.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/example/demo/beautification.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/example/demo/beautification.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/example/demo/bpmnEvent.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/example/demo/bpmnEvent.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/example/demo/bpmnVue.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/example/demo/bpmnVue.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/example/demo/engine.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/example/demo/engine.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/example/demo/nodeRedVue3.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/example/demo/nodeRedVue3.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/example/demo/treelikeDemoVue3.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/example/demo/treelikeDemoVue3.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/example/demo/vueDemo.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/examples/showcase/example/demo/vueDemo.tsx
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/example/index.en.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Example
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/example
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/examples/showcase/example/index.zh.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 示例
3 | order: 1
4 | redirect_from:
5 | - /zh/examples/example
6 | ---
7 |
--------------------------------------------------------------------------------
/sites/docs/public/didi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/public/didi.png
--------------------------------------------------------------------------------
/sites/docs/public/logicflow-8-7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/public/logicflow-8-7.jpg
--------------------------------------------------------------------------------
/sites/docs/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/public/logo.png
--------------------------------------------------------------------------------
/sites/docs/public/overlay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/public/overlay.png
--------------------------------------------------------------------------------
/sites/docs/public/test.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/public/test.jpeg
--------------------------------------------------------------------------------
/sites/docs/public/vue3-app.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/public/vue3-app.gif
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/advanced/edge/reactEdge/index.less:
--------------------------------------------------------------------------------
1 | .App {
2 | font-family: sans-serif;
3 | text-align: center;
4 | }
5 |
6 | .lf-custom-edge-wrapper {
7 | display: flex;
8 | align-items: center;
9 | justify-content: center;
10 | }
11 |
12 | .custom-edge {
13 | flex: 1;
14 | text-align: center;
15 | background-color: white;
16 | border: 1px solid black;
17 | border-radius: 8px;
18 | }
19 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/advanced/node/connect/connectData.ts:
--------------------------------------------------------------------------------
1 | const data = {
2 | nodes: [
3 | {
4 | id: '1',
5 | type: 'rect',
6 | x: 300,
7 | y: 100,
8 | },
9 | {
10 | id: '2',
11 | type: 'circle',
12 | x: 300,
13 | y: 250,
14 | },
15 | {
16 | id: '3',
17 | type: 'hexagonNode',
18 | x: 100,
19 | y: 100,
20 | text: '只能连接到圆',
21 | },
22 | ],
23 | edges: [],
24 | };
25 |
26 | export default data;
27 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/advanced/node/connect/index.less:
--------------------------------------------------------------------------------
1 | .helloworld-app {
2 | width: 100%;
3 |
4 | .app-content {
5 | height: 380px;
6 |
7 | .lf-node-allow {
8 | .lf-basic-shape {
9 | fill: #67c23a;
10 | }
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/advanced/node/htmlNode/htmlData.ts:
--------------------------------------------------------------------------------
1 | const data = {
2 | nodes: [
3 | {
4 | id: '1',
5 | type: 'button-node',
6 | x: 300,
7 | y: 100,
8 | properties: {
9 | name: 'hello',
10 | body: 'world',
11 | },
12 | },
13 | ],
14 | edges: [],
15 | };
16 |
17 | export default data;
18 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/advanced/node/htmlNode/index.less:
--------------------------------------------------------------------------------
1 | .uml-wrapper {
2 | box-sizing: border-box;
3 | width: 100%;
4 | height: 100%;
5 | background: #efdbff;
6 | border: 2px solid #9254de;
7 | border-radius: 10px;
8 | }
9 |
10 | .uml-head {
11 | font-weight: bold;
12 | font-size: 16px;
13 | line-height: 30px;
14 | text-align: center;
15 | }
16 |
17 | .uml-body {
18 | padding: 5px 10px;
19 | font-size: 12px;
20 | border-top: 1px solid #838382;
21 | border-bottom: 1px solid #838382;
22 | }
23 |
24 | .uml-footer {
25 | padding: 5px 10px;
26 | font-size: 14px;
27 | }
28 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/advanced/node/movable/movableData.ts:
--------------------------------------------------------------------------------
1 | const data = {
2 | nodes: [
3 | {
4 | type: 'custom-node',
5 | x: 300,
6 | y: 250,
7 | text: '你好',
8 | children: ['circle-1'],
9 | },
10 | {
11 | type: 'movable-node',
12 | x: 100,
13 | y: 70,
14 | text: '你好',
15 | },
16 | {
17 | id: 'circle-1',
18 | type: 'circle',
19 | x: 300,
20 | y: 250,
21 | text: 'hello world',
22 | },
23 | ],
24 | edges: [],
25 | };
26 |
27 | export default data;
28 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/advanced/node/movable/movableNode.ts:
--------------------------------------------------------------------------------
1 | import LogicFlow, { RectNode, RectNodeModel } from '@logicflow/core';
2 |
3 | class MovableNode extends RectNode {}
4 |
5 | class MovableNodeModel extends RectNodeModel {
6 | initNodeData(data: LogicFlow.NodeConfig) {
7 | super.initNodeData(data);
8 | this.moveRules.push((model, deltaX, deltaY) => {
9 | // 不允许移动到坐标为负值的地方
10 | if (
11 | model.x + deltaX - this.width / 2 < 0 ||
12 | model.y + deltaY - this.height / 2 < 0
13 | ) {
14 | return false;
15 | }
16 | return true;
17 | });
18 | }
19 | }
20 |
21 | export default {
22 | type: 'movable-node',
23 | view: MovableNode,
24 | model: MovableNodeModel,
25 | };
26 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/advanced/node/reactNode/index.less:
--------------------------------------------------------------------------------
1 | .box-title {
2 | width: 82px;
3 | height: 62px;
4 | line-height: 62px;
5 | text-align: center;
6 | background: url('https://dpubstatic.udache.com/static/dpubimg/0oqFX1nvbD/cloud.png');
7 | background-repeat: no-repeat;
8 | background-size: 80px;
9 | }
10 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/advanced/react/index.less:
--------------------------------------------------------------------------------
1 | .viewport {
2 | position: relative;
3 | height: 200px;
4 | overflow: hidden;
5 | }
6 |
7 | .antd-node-wrapper {
8 | display: flex;
9 | align-items: center;
10 | background-color: #cecece60;
11 | border-radius: 8px;
12 | }
13 |
14 | .react-algo-node {
15 | display: flex;
16 | align-items: center;
17 | width: 100%;
18 | height: 100%;
19 | border: 1px solid #e59b68;
20 | border-radius: 14px;
21 |
22 | img {
23 | width: 24px;
24 | height: 24px;
25 | }
26 |
27 | span {
28 | margin-left: 4px;
29 | color: #000000a6;
30 | font-size: 12px;
31 | }
32 |
33 | &.dark {
34 | background-color: #141414;
35 |
36 | span {
37 | color: #fff;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/basic/edge/arrow/index.tsx:
--------------------------------------------------------------------------------
1 | import LogicFlow from '@logicflow/core';
2 | import '@logicflow/core/dist/index.css';
3 | import { useEffect, useRef } from 'react';
4 |
5 | import CustomArrow from './customArrow';
6 | import data from './data';
7 |
8 | const SilentConfig = {
9 | isSilentMode: true,
10 | stopScrollGraph: true,
11 | stopMoveGraph: true,
12 | stopZoomGraph: true,
13 | adjustNodePosition: true,
14 | };
15 |
16 | export default function App() {
17 | const refContainer = useRef(null);
18 | useEffect(() => {
19 | const lf = new LogicFlow({
20 | container: refContainer.current!,
21 | grid: true,
22 | height: 400,
23 | ...SilentConfig,
24 | });
25 | lf.register(CustomArrow);
26 | lf.render(data);
27 | lf.translateCenter();
28 | });
29 | return ;
30 | }
31 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/basic/edge/shapes/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import LogicFlow from '@logicflow/core';
3 | import '@logicflow/core/es/index.css';
4 |
5 | import data from './data';
6 | import '../../../index.less';
7 |
8 | const SilentConfig = {
9 | stopScrollGraph: true,
10 | stopMoveGraph: true,
11 | stopZoomGraph: true,
12 | };
13 |
14 | export default class Example extends React.Component {
15 | private container!: HTMLDivElement;
16 |
17 | componentDidMount() {
18 | const lf = new LogicFlow({
19 | container: this.container,
20 | grid: true,
21 | ...SilentConfig,
22 | });
23 |
24 | lf.render(data);
25 | lf.translateCenter();
26 | }
27 |
28 | refContainer = (container: HTMLDivElement) => {
29 | this.container = container;
30 | };
31 |
32 | render() {
33 | return (
34 |
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/basic/edge/textPosition/data.ts:
--------------------------------------------------------------------------------
1 | const data = {
2 | nodes: [
3 | {
4 | id: 'rect_1',
5 | type: 'rect',
6 | x: 150,
7 | y: 100,
8 | text: 'rect',
9 | },
10 | {
11 | id: 'circle_1',
12 | type: 'circle',
13 | x: 450,
14 | y: 300,
15 | text: 'circle',
16 | },
17 | ],
18 | edges: [
19 | {
20 | sourceNodeId: 'rect_1',
21 | targetNodeId: 'circle_1',
22 | type: 'custom-edge',
23 | text: '连线文本',
24 | },
25 | ],
26 | };
27 |
28 | export default data;
29 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/basic/instance/graphData/index.less:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 500px;
3 | }
4 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/basic/node/custom/customCircle.ts:
--------------------------------------------------------------------------------
1 | import LogicFlow, {
2 | CircleNode,
3 | CircleNodeModel,
4 | GraphModel,
5 | } from '@logicflow/core';
6 |
7 | class CustomCircleModel extends CircleNodeModel {
8 | constructor(data: LogicFlow.NodeConfig, graphModel: GraphModel) {
9 | data.text = {
10 | // 自定义文本坐标:向下移动40px
11 | value: data.text as string,
12 | x: data.x,
13 | y: data.y + 40,
14 | };
15 | super(data, graphModel);
16 |
17 | // 半径:控制圆形大小
18 | this.r = 20;
19 | }
20 | }
21 |
22 | export default {
23 | type: 'custom-circle',
24 | view: CircleNode,
25 | model: CustomCircleModel,
26 | };
27 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/basic/node/custom/customData.ts:
--------------------------------------------------------------------------------
1 | const data = {
2 | nodes: [
3 | {
4 | id: 'node_id_1',
5 | type: 'custom-circle',
6 | x: 100,
7 | y: 60,
8 | text: '自定义圆形',
9 | },
10 | {
11 | id: 'node_id_2',
12 | type: 'custom-ellipse',
13 | x: 300,
14 | y: 60,
15 | text: '自定义椭圆',
16 | },
17 | {
18 | id: 'node_id_4',
19 | type: 'custom-diamond',
20 | x: 500,
21 | y: 60,
22 | text: '自定义菱形',
23 | },
24 | {
25 | id: 'node_id_3',
26 | type: 'custom-polygon',
27 | x: 110,
28 | y: 220,
29 | text: '自定义多边形',
30 | },
31 | {
32 | id: 'node_id_5',
33 | type: 'custom-rect',
34 | x: 350,
35 | y: 220,
36 | text: '自定义矩形',
37 | },
38 | ],
39 | edges: [],
40 | };
41 |
42 | export default data;
43 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/basic/node/custom/customDiamond.ts:
--------------------------------------------------------------------------------
1 | import LogicFlow, {
2 | DiamondNode,
3 | DiamondNodeModel,
4 | GraphModel,
5 | } from '@logicflow/core';
6 |
7 | class CustomDiamondModel extends DiamondNodeModel {
8 | constructor(data: LogicFlow.NodeConfig, graphModel: GraphModel) {
9 | data.text = {
10 | // 自定义文本坐标:向下移动40px
11 | value: data.text as string,
12 | x: data.x,
13 | y: data.y + 40,
14 | };
15 | super(data, graphModel);
16 |
17 | this.rx = 50;
18 | this.ry = 20;
19 | }
20 | }
21 |
22 | export default {
23 | type: 'custom-diamond',
24 | view: DiamondNode,
25 | model: CustomDiamondModel,
26 | };
27 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/basic/node/custom/customEllipse.ts:
--------------------------------------------------------------------------------
1 | import LogicFlow, {
2 | EllipseNode,
3 | EllipseNodeModel,
4 | GraphModel,
5 | } from '@logicflow/core';
6 |
7 | class CustomEllipseModel extends EllipseNodeModel {
8 | constructor(data: LogicFlow.NodeConfig, graphModel: GraphModel) {
9 | if (data.text && typeof data.text === 'string') {
10 | data.text = {
11 | // 自定义文本坐标:向下移动40px
12 | value: data.text,
13 | x: data.x,
14 | y: data.y + 40,
15 | };
16 | }
17 | super(data, graphModel);
18 |
19 | // rx:x轴的半径 ry:y轴的半径,通过rx,ry控制椭圆大小
20 | this.rx = 50;
21 | this.ry = 20;
22 | }
23 | }
24 |
25 | export default {
26 | type: 'custom-ellipse',
27 | view: EllipseNode,
28 | model: CustomEllipseModel,
29 | };
30 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/basic/node/properties/customRect.ts:
--------------------------------------------------------------------------------
1 | import { RectNode, RectNodeModel } from '@logicflow/core';
2 |
3 | class CustomRectModel extends RectNodeModel {
4 | getNodeStyle() {
5 | const style = super.getNodeStyle();
6 |
7 | const properties = this.properties;
8 | if (properties.statu === 'pass') {
9 | // 业务属性statu为‘pass’时 展示边框颜色为green
10 | style.stroke = 'green';
11 | } else if (properties.statu === 'reject') {
12 | // 业务属性statu为‘reject’时 展示边框颜色为red
13 | style.stroke = 'red';
14 | } else {
15 | style.stroke = 'rgb(24, 125, 255)';
16 | }
17 | return style;
18 | }
19 | }
20 |
21 | class CustomRectNode extends RectNode {}
22 |
23 | export default {
24 | type: 'custom-rect',
25 | view: CustomRectNode,
26 | model: CustomRectModel,
27 | };
28 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/basic/node/properties/data.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | nodes: [
3 | {
4 | id: '1',
5 | type: 'custom-rect',
6 | x: 100,
7 | y: 100,
8 | text: 'default',
9 | properties: {
10 | width: 70,
11 | height: 70,
12 | },
13 | },
14 | {
15 | id: '2',
16 | type: 'custom-rect',
17 | x: 300,
18 | y: 100,
19 | text: 'pass',
20 | properties: {
21 | statu: 'pass', // 业务属性
22 | width: 100, // 形状属性
23 | height: 100,
24 | radius: 20,
25 | style: {
26 | // 样式属性
27 | strokeWidth: 3,
28 | },
29 | },
30 | },
31 | {
32 | id: '3',
33 | type: 'custom-rect',
34 | x: 500,
35 | y: 100,
36 | text: 'reject',
37 | properties: {
38 | statu: 'reject',
39 | width: 130,
40 | height: 130,
41 | },
42 | },
43 | ],
44 | };
45 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/basic/node/properties/index.tsx:
--------------------------------------------------------------------------------
1 | import LogicFlow from '@logicflow/core';
2 | import '@logicflow/core/dist/index.css';
3 | import { useEffect, useRef } from 'react';
4 |
5 | import UserTask from './customRect';
6 | import data from './data';
7 |
8 | const SilentConfig = {
9 | isSilentMode: true,
10 | stopScrollGraph: true,
11 | stopMoveGraph: true,
12 | stopZoomGraph: true,
13 | adjustNodePosition: true,
14 | };
15 |
16 | export default function App() {
17 | const refContainer = useRef(null);
18 | useEffect(() => {
19 | const lf = new LogicFlow({
20 | container: refContainer.current!,
21 | grid: true,
22 | height: 200,
23 | ...SilentConfig,
24 | });
25 | lf.register(UserTask);
26 | lf.render(data);
27 | lf.translateCenter();
28 | });
29 | return ;
30 | }
31 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/components/edges/custom-curved-polyline/index.tsx:
--------------------------------------------------------------------------------
1 | import { LogicFlow } from '@logicflow/core';
2 | import { CurvedEdge, CurvedEdgeModel } from '@logicflow/extension';
3 |
4 | class CustomCurvedEdge extends CurvedEdge {}
5 |
6 | class CustomCurvedEdgeModel extends CurvedEdgeModel {
7 | initEdgeData(data: LogicFlow.EdgeData) {
8 | super.initEdgeData(data);
9 | this.radius = 20;
10 | }
11 | getEdgeStyle() {
12 | const style = super.getEdgeStyle();
13 | style.strokeWidth = 3;
14 | return style;
15 | }
16 | }
17 |
18 | export default {
19 | type: 'customCurvedEdge',
20 | model: CustomCurvedEdgeModel,
21 | view: CustomCurvedEdge,
22 | };
23 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/components/nodes/custom-html/Text.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/didi/LogicFlow/f614a29a5aa563e55224ea06d9af49cc1c9630a5/sites/docs/src/tutorial/components/nodes/custom-html/Text.tsx
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/components/nodes/custom-html/index.ts:
--------------------------------------------------------------------------------
1 | import CustomHtml from './Html';
2 | import CustomIcon from './Icon';
3 | import CustomImage from './Image';
4 | // import CustomText from './Text';
5 |
6 | export { CustomIcon, CustomImage, CustomHtml };
7 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/extension/dynamic-group/index.module.less:
--------------------------------------------------------------------------------
1 | .viewport {
2 | position: relative;
3 | height: 450px;
4 | overflow: hidden;
5 |
6 | :global {
7 | .lf-dnd-shape {
8 | background-size: contain;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/extension/dynamic-group/nodes/index.ts:
--------------------------------------------------------------------------------
1 | export * from './custom-group';
2 | export * from './sub-process';
3 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/extension/globals.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.less' {
2 | const content: Record;
3 | export default content;
4 | }
5 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/extension/label/index.module.less:
--------------------------------------------------------------------------------
1 | .viewport {
2 | position: relative;
3 | height: 450px;
4 | overflow: hidden;
5 | }
6 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/extension/mini-map/index.module.less:
--------------------------------------------------------------------------------
1 | .viewport {
2 | position: relative;
3 | height: calc(100vh - 270px);
4 | overflow: hidden;
5 | }
6 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/extension/proximity-connect/index.less:
--------------------------------------------------------------------------------
1 | .viewport {
2 | position: relative;
3 | height: 80vh;
4 | overflow: hidden;
5 | }
6 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/extension/selection-select/index.module.less:
--------------------------------------------------------------------------------
1 | .viewport {
2 | position: relative;
3 | height: calc(100vh - 250px);
4 | overflow: hidden;
5 | }
6 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/extension/snapshot/index.module.less:
--------------------------------------------------------------------------------
1 | .graph {
2 | width: 90%;
3 | height: 500px;
4 |
5 | :global {
6 | .lf-dndpanel .lf-dnd-shape {
7 | width: 20px !important;
8 | height: 20px !important;
9 | }
10 |
11 | .uml-wrapper {
12 | box-sizing: border-box;
13 | width: 100%;
14 | height: 100%;
15 | background: #efdbff;
16 | border: 2px solid #9254de;
17 | border-radius: 10px;
18 | }
19 |
20 | .uml-head {
21 | font-weight: bold;
22 | font-size: 16px;
23 | line-height: 30px;
24 | text-align: center;
25 | }
26 |
27 | .uml-body {
28 | padding: 5px 10px;
29 | font-size: 12px;
30 | border-top: 1px solid #9254de;
31 | border-bottom: 1px solid #9254de;
32 | }
33 |
34 | .uml-footer {
35 | padding: 5px 10px;
36 | font-size: 14px;
37 | }
38 | }
39 | }
40 |
41 | .input {
42 | width: 280px;
43 | }
44 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/get-started/helloworld/data.ts:
--------------------------------------------------------------------------------
1 | const data = {
2 | nodes: [
3 | {
4 | id: '1',
5 | type: 'rect',
6 | x: 100,
7 | y: 100,
8 | text: '节点1',
9 | },
10 | {
11 | id: '2',
12 | type: 'circle',
13 | x: 300,
14 | y: 100,
15 | text: '节点2',
16 | },
17 | ],
18 | edges: [
19 | {
20 | sourceNodeId: '1',
21 | targetNodeId: '2',
22 | type: 'polyline',
23 | text: '连线',
24 | startPoint: {
25 | x: 140,
26 | y: 100,
27 | },
28 | endPoint: {
29 | x: 250,
30 | y: 100,
31 | },
32 | },
33 | ],
34 | };
35 |
36 | export default data;
37 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/get-started/index.less:
--------------------------------------------------------------------------------
1 | .getting-started.helloworld-app {
2 | width: 100%;
3 |
4 | .app-content {
5 | height: 200px;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/sites/docs/src/tutorial/index.less:
--------------------------------------------------------------------------------
1 | .helloworld-app {
2 | width: 100%;
3 |
4 | .app-content {
5 | height: 380px;
6 | }
7 | }
8 |
9 | .viewport {
10 | position: relative;
11 | height: 80vh;
12 | overflow: hidden;
13 | }
14 |
--------------------------------------------------------------------------------
/sites/docs/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "jsx": "react-jsx",
4 | "jsxImportSource": "react",
5 | "strict": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "baseUrl": "./",
9 | "resolveJsonModule": true,
10 | "paths": {
11 | "@@/*": [".dumi/tmp/*"]
12 | }
13 | },
14 | "exclude": ["examples/**/*.tsx"],
15 | "include": [".dumirc.ts", "src/**/*"]
16 | }
17 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "tasks": {
4 | "clean": {
5 | "outputs": []
6 | },
7 | "build:esm": {
8 | "dependsOn": ["^build:dev"],
9 | "outputs": ["es/**", "src/style/raw.ts"]
10 | },
11 | "build:cjs": {
12 | "dependsOn": ["^build:dev"],
13 | "outputs": ["lib/**", "src/style/raw.ts"]
14 | },
15 | "build:umd": {
16 | "dependsOn": ["^build:dev"],
17 | "outputs": ["dist/**", "src/style/raw.ts"]
18 | },
19 | "build:dev": {
20 | "dependsOn": ["^build:dev"],
21 | "outputs": ["lib/**", "es/**", "src/style/raw.ts"]
22 | },
23 | "build": {
24 | "dependsOn": ["^build:dev"],
25 | "outputs": ["lib/**", "es/**", "dist/**", "src/style/raw.ts"]
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------