Browse Source

feat: supports server storage

master
smallstonesk 4 years ago
parent
commit
b30d5a7b7f
  1. 3
      packages/core/package.json
  2. 38
      packages/core/src/api/index.ts
  3. 21
      packages/core/src/mods/flowChart/createFlowChart.ts
  4. 23
      packages/core/src/mods/flowChart/index.tsx
  5. 96
      packages/core/src/mods/flowChart/registerServerStorage.ts
  6. 2
      packages/core/src/mods/toolBar/index.tsx
  7. 4
      packages/core/src/mods/toolBar/widgets/index.module.less
  8. 69
      packages/core/src/mods/toolBar/widgets/modifyStatus.tsx
  9. 13
      packages/core/src/utils/index.ts

3
packages/core/package.json

@ -40,11 +40,14 @@
"@antv/x6": "^0.13.0",
"ace-builds": "^1.4.12",
"antd": "^4.5.3",
"lodash.merge": "^4.6.2",
"query-string": "^6.13.7",
"react-ace": "^9.1.3",
"react-color": "^2.18.1",
"react-json-view": "^1.19.1"
},
"devDependencies": {
"@types/lodash.merge": "^4.6.6",
"@types/react": "^16.9.34",
"@types/react-color": "^3.0.4",
"@types/react-dom": "^16.9.7",

38
packages/core/src/api/index.ts

@ -3,10 +3,18 @@ export interface ILocalConfig {
port: string;
}
export type ActionType = 'create' | 'update' | 'remove';
export interface IModifyGraphAction {
type: string;
actionType: ActionType;
data: any;
}
export const localConfig: ILocalConfig = {
ip: '127.0.0.1',
port: '3500'
};
}
export const updateLocalConfig = (config: ILocalConfig) => {
localConfig.ip = config.ip || localConfig.ip;
@ -27,3 +35,31 @@ export const localSave = (data: any) => {
body: JSON.stringify(data)
});
}
export const queryGraph = (projectId: string) => {
return fetch('/api/queryGraph', {
method: 'POST',
headers: {'Content-Type': 'application/json;charset=utf-8'},
body: JSON.stringify({
projectId
})
}).then(res => res.json()).then(res => {
const {success} = res;
if(success) {
return res;
} else {
return Promise.reject(res);
}
})
}
export const modifyGraph = (projectId: string, actions: IModifyGraphAction[]) => {
return fetch('/api/modifyGraph', {
method: 'POST',
headers: {'Content-Type': 'application/json;charset=utf-8'},
body: JSON.stringify({
projectId,
actions
})
});
}

21
packages/core/src/mods/flowChart/createFlowChart.ts

@ -3,6 +3,7 @@ import {Cell, Edge, Graph, Node} from '@antv/x6';
import {MIN_ZOOM, MAX_ZOOM} from '../../common/const';
import baseCellSchemaMap from '../../common/baseCell';
import previewCellSchemaMap from '../../common/previewCell';
import {registerServerStorage} from './registerServerStorage';
import MiniMapSimpleNode from '../../components/miniMapSimpleNode';
// X6 register base/preview cell shape
@ -32,25 +33,6 @@ const registerEvents = (flowChart: Graph): void => {
}
}
});
flowChart.on('edge:mouseenter', (args) => {
const cell = args.cell as Cell;
cell.addTools([
{
name: 'target-arrowhead',
args: {
attrs: {
d: 'M -10.5 -6 1 0 -10.5 6 Z',
'stroke-width': 0,
fill: '#333'
}
}
}
]);
});
flowChart.on('edge:mouseleave', (args) => {
const cell = args.cell as Cell;
cell.removeTools(['target-arrowhead']);
});
};
const registerShortcuts = (flowChart: Graph): void => {
@ -156,6 +138,7 @@ const createFlowChart = (container: HTMLDivElement, miniMapContainer: HTMLDivEle
});
registerEvents(flowChart);
registerShortcuts(flowChart);
registerServerStorage(flowChart);
return flowChart;
};

23
packages/core/src/mods/flowChart/index.tsx

@ -1,8 +1,11 @@
import React, {useRef, useEffect} from 'react';
import React, { useRef, useEffect } from 'react';
import styles from './index.module.less';
import {Graph} from '@antv/x6';
import { message } from 'antd';
import { Graph } from '@antv/x6';
import { queryGraph } from '../../api';
import { parseQuery } from '../../utils';
import createFlowChart from './createFlowChart';
interface IProps {
@ -10,15 +13,29 @@ interface IProps {
}
const FlowChart: React.FC<IProps> = props => {
const {onReady} = props;
const graphRef = useRef<HTMLDivElement>(null);
const miniMapRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if(graphRef.current && miniMapRef.current) {
if (graphRef.current && miniMapRef.current) {
const graph = createFlowChart(graphRef.current, miniMapRef.current);
onReady(graph);
fetchData(graph);
}
}, []);
const fetchData = (graph: Graph) => {
const { projectId } = parseQuery();
queryGraph(projectId as string).then(res => {
const { data: dsl } = res;
graph.fromJSON(dsl);
}).catch(err => {
message.error(err.msg);
});
};
return (
<div className={styles.container}>
<div className={styles.flowChart} ref={graphRef}/>

96
packages/core/src/mods/flowChart/registerServerStorage.ts

@ -0,0 +1,96 @@
import { Graph } from '@antv/x6';
import merge from 'lodash.merge';
import { parseQuery } from '../../utils';
import { modifyGraph, ActionType, IModifyGraphAction } from '../../api';
const { projectId } = parseQuery();
const memQueue: IModifyGraphAction[] = [];
const validate = (type: string, data: any) => {
if (type === 'node') {
return true;
} else if (type === 'edge') {
const { source, target } = data;
return source.cell && target.cell;
} else {
return false;
}
}
const enqueue = (type: string, actionType: ActionType, data: any) => {
if (!validate(type, data)) {
return;
}
const foundIndex = memQueue.findIndex(item => item.type === type && item.actionType === actionType);
if (foundIndex > -1) {
const deleted = memQueue.splice(foundIndex, 1)[0];
data = merge(deleted, data);
}
memQueue.push({ type, actionType, data });
};
let modifyActionTimer = -1;
const save = (flowChart: Graph, type: string, actionType: ActionType, data: any) => {
enqueue(type, actionType, data);
clearTimeout(modifyActionTimer);
modifyActionTimer = setTimeout(() => {
const pushedActions = memQueue.slice(0);
if(pushedActions.length > 0) {
flowChart.trigger('graph:change:modify');
modifyGraph(projectId, memQueue).then(res => {
memQueue.splice(0, pushedActions.length);
flowChart.trigger('graph:modified', {success: true});
}).catch(err => {
flowChart.trigger('graph:modified', {success: true, error: err});
});
}
}, 100);
};
type ActionEventMap = {[key: string]: string[]};
const NODE_ACTION_EVENT_MAP: ActionEventMap = {
create: ['node:added'],
remove: ['node:removed'],
update: [
'node:moved',
'node:resized',
'node:rotated',
'node:change:ports',
'node:change:attrs',
'node:change:data',
'node:change:zIndex'
]
};
const EDGE_ACTION_EVENT_MAP: ActionEventMap = {
create: ['edge:connected'],
remove: ['edge:removed'],
update: [
'edge:moved',
]
};
export const registerServerStorage = (flowChart: Graph) => {
Object.keys(NODE_ACTION_EVENT_MAP).forEach((actionType) => {
const events = NODE_ACTION_EVENT_MAP[actionType];
events.forEach(event => {
flowChart.on(event, (args: any) => {
console.log('node event:', event, 'args:', args);
save(flowChart, 'node', actionType as ActionType, args.node.toJSON());
});
});
});
Object.keys(EDGE_ACTION_EVENT_MAP).forEach((actionType) => {
const events = EDGE_ACTION_EVENT_MAP[actionType];
events.forEach(event => {
flowChart.on(event, (args: any) => {
console.log('edge event:', event, 'args:', args);
save(flowChart, 'edge', actionType as ActionType, args.edge.toJSON());
});
});
});
};

2
packages/core/src/mods/toolBar/index.tsx

@ -5,6 +5,7 @@ import styles from './index.module.less';
import {Graph} from '@antv/x6';
import widgets from './widgets';
import ModifyStatus from './widgets/modifyStatus';
interface IProps {
flowChart: Graph;
@ -31,6 +32,7 @@ const ToolBar: React.FC<IProps> = props => {
})}
</div>
))}
<ModifyStatus flowChart={flowChart}/>
</div>
);
};

4
packages/core/src/mods/toolBar/widgets/index.module.less

@ -50,6 +50,10 @@
}
}
.modifyStatusContainer {
margin-left: 8px;
}
.bgColorContainer, .textColorContainer, .borderColorContainer {
position: relative;

69
packages/core/src/mods/toolBar/widgets/modifyStatus.tsx

@ -0,0 +1,69 @@
import React, { useEffect, useState } from 'react';
import styles from './index.module.less';
import { Graph } from '@antv/x6';
enum Status {
pending = 0,
syncing = 1,
successful = 2,
failed = 3
}
interface IProps {
flowChart: Graph
}
const STATUS_TEXT_MAP = {
[Status.pending]: '',
[Status.syncing]: {
color: '#999',
text: '正在保存...'
},
[Status.successful]: {
color: '#999',
text: '所有更改已保存'
},
[Status.failed]: {
color: '#EC5B56',
text: '同步失败,进入离线模式'
}
};
const ModifyStatus: React.FC<IProps> = (props) => {
const {flowChart} = props;
const [status, setStatus] = useState<Status>(Status.pending);
useEffect(() => {
flowChart.on('graph:change:modify', () => {
setStatus(Status.syncing);
});
flowChart.on('graph:modified', (res: any) => {
const {success} = res;
if(success) {
setStatus(Status.successful);
} else {
setStatus(Status.failed);
}
});
return () => {
flowChart.off('graph:change:modify');
flowChart.off('graph:modified');
};
}, []);
if(status === Status.pending) {
return null;
} else {
const {color, text} = STATUS_TEXT_MAP[status];
return (
<div className={styles.modifyStatusContainer}>
<span style={{color}}>{text}</span>
</div>
);
}
};
export default ModifyStatus;

13
packages/core/src/utils/index.ts

@ -1,3 +1,5 @@
import { parse } from 'query-string';
export const safeParse = (json: string): Object => {
try {
return JSON.parse(json);
@ -24,3 +26,14 @@ export const safeGet = (obj: any, keyChain: string, defaultVal?: any): any => {
return retVal;
};
const PARSE_CONFIG = {
skipNull: true,
skipEmptyString: true,
parseNumbers: false,
parseBooleans: false,
};
export const parseQuery = (): {[key: string]: any} => {
return parse(location.search, PARSE_CONFIG);
};

Loading…
Cancel
Save