Browse Source

feat: eslint代码规范

master
yangpei 4 years ago
parent
commit
265e0ff322
  1. 22
      .eslintrc.js
  2. 3
      config/cli.jest.config.js
  3. 3
      config/core.jest.config.js
  4. 3
      config/editor.jest.config.js
  5. 3
      config/plugin.jest.config.js
  6. 15
      package-lock.json
  7. 12
      package.json
  8. 1
      packages/cli/package.json
  9. 3
      packages/cli/src/cmd/base/index.js
  10. 12
      packages/cli/src/cmd/dev/addPlugins.js
  11. 15
      packages/cli/src/cmd/dev/extractCodes.js
  12. 24
      packages/cli/src/cmd/dev/index.js
  13. 24
      packages/cli/src/cmd/dev/mergePkg.js
  14. 10
      packages/cli/src/cmd/dev/simplifyDSL.js
  15. 7
      packages/cli/src/cmd/dev/template/context.js
  16. 2
      packages/cli/src/cmd/dev/template/index.js
  17. 74
      packages/cli/src/cmd/dev/template/logic.js
  18. 2
      packages/cli/src/cmd/index.js
  19. 1
      packages/cli/src/cmd/init/index.js
  20. 6
      packages/cli/src/utils/getConfig.js
  21. 10
      packages/cli/src/utils/server/index.js
  22. 50
      packages/core/src/api/index.ts
  23. 32
      packages/core/src/common/baseCell/behavior.ts
  24. 54
      packages/core/src/common/baseCell/branch.ts
  25. 4
      packages/core/src/common/baseCell/index.ts
  26. 32
      packages/core/src/common/baseCell/start.ts
  27. 10
      packages/core/src/common/previewCell/behavior.ts
  28. 10
      packages/core/src/common/previewCell/branch.ts
  29. 4
      packages/core/src/common/previewCell/index.ts
  30. 8
      packages/core/src/common/previewCell/start.ts
  31. 75
      packages/core/src/common/shortcuts.ts
  32. 15
      packages/core/src/components/colorPicker/index.tsx
  33. 5
      packages/core/src/components/miniMapSimpleNode/index.tsx
  34. 4
      packages/core/src/components/xIcon/index.tsx
  35. 12
      packages/core/src/index.tsx
  36. 59
      packages/core/src/mods/flowChart/createFlowChart.ts
  37. 17
      packages/core/src/mods/flowChart/index.tsx
  38. 34
      packages/core/src/mods/flowChart/registerServerStorage.ts
  39. 40
      packages/core/src/mods/header/connectStatus/editModal.tsx
  40. 43
      packages/core/src/mods/header/connectStatus/index.tsx
  41. 10
      packages/core/src/mods/header/index.tsx
  42. 31
      packages/core/src/mods/layout/index.tsx
  43. 14
      packages/core/src/mods/settingBar/components/checkbox/index.tsx
  44. 28
      packages/core/src/mods/settingBar/components/code/index.tsx
  45. 10
      packages/core/src/mods/settingBar/components/input/index.tsx
  46. 34
      packages/core/src/mods/settingBar/components/json/index.tsx
  47. 31
      packages/core/src/mods/settingBar/index.tsx
  48. 33
      packages/core/src/mods/settingBar/mods/basic/index.tsx
  49. 41
      packages/core/src/mods/settingBar/mods/config/index.tsx
  50. 43
      packages/core/src/mods/sideBar/index.tsx
  51. 15
      packages/core/src/mods/toolBar/index.tsx
  52. 18
      packages/core/src/mods/toolBar/widgets/bgColor.tsx
  53. 16
      packages/core/src/mods/toolBar/widgets/bold.tsx
  54. 20
      packages/core/src/mods/toolBar/widgets/borderColor.tsx
  55. 12
      packages/core/src/mods/toolBar/widgets/bringToBack.tsx
  56. 14
      packages/core/src/mods/toolBar/widgets/bringToTop.tsx
  57. 20
      packages/core/src/mods/toolBar/widgets/common/makeBtnWidget.tsx
  58. 20
      packages/core/src/mods/toolBar/widgets/common/makeDropdownWidget.tsx
  59. 12
      packages/core/src/mods/toolBar/widgets/fitWindow.tsx
  60. 20
      packages/core/src/mods/toolBar/widgets/fontSize.tsx
  61. 48
      packages/core/src/mods/toolBar/widgets/horizontalAlign.tsx
  62. 4
      packages/core/src/mods/toolBar/widgets/index.tsx
  63. 16
      packages/core/src/mods/toolBar/widgets/italic.tsx
  64. 34
      packages/core/src/mods/toolBar/widgets/lineStyle.tsx
  65. 25
      packages/core/src/mods/toolBar/widgets/modifyStatus.tsx
  66. 12
      packages/core/src/mods/toolBar/widgets/redo.tsx
  67. 12
      packages/core/src/mods/toolBar/widgets/save.tsx
  68. 18
      packages/core/src/mods/toolBar/widgets/textColor.tsx
  69. 16
      packages/core/src/mods/toolBar/widgets/underline.tsx
  70. 12
      packages/core/src/mods/toolBar/widgets/undo.tsx
  71. 54
      packages/core/src/mods/toolBar/widgets/verticalAlign.tsx
  72. 34
      packages/core/src/mods/toolBar/widgets/zoom.tsx
  73. 4
      packages/core/src/typings.d.ts
  74. 8
      packages/core/src/utils/flowChartUtils.ts
  75. 13
      packages/core/src/utils/index.ts
  76. 4
      packages/plugin-store/src/index.ts

22
.eslintrc.js

@ -2,11 +2,10 @@ module.exports = {
extends: [
'standard',
'plugin:import/typescript',
'plugin:unicorn/recommended',
'prettier',
'prettier/react',
],
plugins: ['prettier', 'unicorn'],
plugins: ['prettier', 'react'],
env: {
node: true,
browser: true,
@ -15,13 +14,15 @@ module.exports = {
'import/no-extraneous-dependencies': ['error', { peerDependencies: true }],
'prettier/prettier': 'error',
// 修复 tsx 文件引用 tsx 文件报错的问题
'react/jsx-filename-extension': ['error', { extensions: ['.ts', '.tsx'] }],
'unicorn/prevent-abbreviations': 'off',
'react/jsx-filename-extension': ['warn', { extensions: ['.ts', '.tsx'] }],
"semi": [2, "always"],//语句强制分号结尾
"semi-spacing": [0, { "before": false, "after": true }],//分号前后空格
"no-var": 0,//禁用var,用let和const代替
"no-use-before-define": 2,//未定义前不能使用
"no-unused-expressions": 2,//禁止无用的表达式
"space-before-function-paren": 'off',
'comma-dangle': 'off',
'import/extensions': 'off',
},
overrides: [{
files: ['*.ts', '*.tsx'],
@ -35,15 +36,10 @@ module.exports = {
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'no-param-reassign': 'off',
'import/extensions': [
// airbnb 配置导致 tsx 文件引用一直报错
'error',
'ignorePackages',
{
ts: 'never',
tsx: 'never',
},
],
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-unused-vars': 0,
'@typescript-eslint/explicit-function-return-type': 0,
},
},],
};

3
config/cli.jest.config.js

@ -7,5 +7,6 @@ module.exports = {
moduleFileExtensions: ['js', 'json', 'jsx', 'node'],
testMatch: [
"<rootDir>/__tests__/cli/src/**/*.(spec|test).[jt]s?(x)"
]
],
preset: "jest-puppeteer"
};

3
config/core.jest.config.js

@ -7,5 +7,6 @@ module.exports = {
moduleFileExtensions: ['js', 'json', 'jsx', 'node'],
testMatch: [
"<rootDir>/__tests__/core/src/**/*.(spec|test).[jt]s?(x)"
]
],
preset: "jest-puppeteer"
};

3
config/editor.jest.config.js

@ -7,5 +7,6 @@ module.exports = {
moduleFileExtensions: ['js', 'json', 'jsx', 'node'],
testMatch: [
"<rootDir>/__tests__/json-schema-editor/src/**/*.(spec|test).[jt]s?(x)"
]
],
preset: "jest-puppeteer"
};

3
config/plugin.jest.config.js

@ -7,5 +7,6 @@ module.exports = {
moduleFileExtensions: ['js', 'json', 'jsx', 'node'],
testMatch: [
"<rootDir>/__tests__/plugin-store/src/**/*.(spec|test).[jt]s?(x)"
]
],
preset: "jest-puppeteer"
};

15
package-lock.json

@ -6936,10 +6936,9 @@
"dev": true
},
"eventemitter3": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==",
"dev": true
"version": "4.0.7",
"resolved": "https://registry.npm.taobao.org/eventemitter3/download/eventemitter3-4.0.7.tgz?cache=0&sync_timestamp=1598517809015&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feventemitter3%2Fdownload%2Feventemitter3-4.0.7.tgz",
"integrity": "sha1-Lem2j2Uo1WRO9cWVJqG0oHMGFp8="
},
"events": {
"version": "3.1.0",
@ -10172,6 +10171,14 @@
"dev": true,
"requires": {
"eventemitter3": "^3.1.0"
},
"dependencies": {
"eventemitter3": {
"version": "3.1.2",
"resolved": "https://registry.npm.taobao.org/eventemitter3/download/eventemitter3-3.1.2.tgz?cache=0&sync_timestamp=1598517809015&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Feventemitter3%2Fdownload%2Feventemitter3-3.1.2.tgz",
"integrity": "sha1-LT1I+cNGaY/Og6hdfWZOmFNd9uc=",
"dev": true
}
}
},
"p-reduce": {

12
package.json

@ -2,9 +2,7 @@
"name": "root",
"private": true,
"scripts": {
"lint-eslint": "eslint --cache --ext .js,.jsx,.ts,.tsx ./packages/**/src",
"check-types": "tsc --noEmit",
"lint": "npm run lint-eslint & npm run check-types",
"lint": "eslint --cache --fix --ext .ts,tsx,js,jsx ./packages/**/src > ./.eslint-error.log; exit 0",
"test": "jest --coverage",
"e2e": "cypress open",
"doc": "dumi dev"
@ -32,30 +30,34 @@
"@rollup/plugin-strip": "^1.3.2",
"@rollup/plugin-url": "^4.0.2",
"@svgr/rollup": "^5.3.1",
"@types/jest": "^26.0.16",
"@typescript-eslint/eslint-plugin": "^2.29.0",
"@typescript-eslint/parser": "^2.29.0",
"cypress": "^6.0.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.1.0",
"eslint-config-prettier": "^6.10.1",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-react": "^7.19.0",
"eslint-plugin-react-hooks": "^3.0.0",
"eslint-plugin-unicorn": "^18.0.1",
"eslint-plugin-standard": "^5.0.0",
"jest": "^26.6.3",
"jest-environment-jsdom-fourteen": "^1.0.1",
"jest-puppeteer": "^4.4.0",
"lerna": "^3.20.2",
"prettier": "^2.0.4",
"puppeteer": "^5.5.0",
"react-app-polyfill": "^2.0.0",
"rollup": "^2.6.1",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-size-snapshot": "^0.11.0",
"snazzy": "^9.0.0",
"standard": "^16.0.3",
"ts-jest": "^26.4.4",
"typescript": "^3.8.3"
},
"dependencies": {

1
packages/cli/package.json

@ -23,6 +23,7 @@
"body-parser": "^1.19.0",
"commander": "^6.0.0",
"cors": "^2.8.5",
"eventemitter3": "^4.0.7",
"express": "^4.17.1",
"fs-extra": "^9.0.1"
}

3
packages/cli/src/cmd/base/index.js

@ -1,6 +1,5 @@
class Base {
constructor({config}) {
constructor({ config }) {
this._config = config;
return this;
}

12
packages/cli/src/cmd/dev/addPlugins.js

@ -1,22 +1,18 @@
const path = require('path');
const fs = require('fs-extra');
const INSERT_IMPORT_PLUGINS_COMMENT = '\/\/ import plugins here';
const INSERT_USE_PLUGINS_COMMENT = '\/\/ use plugins here';
const INSERT_IMPORT_PLUGINS_COMMENT = '// import plugins here';
const INSERT_USE_PLUGINS_COMMENT = '// use plugins here';
const setup = async (plugins = [], logicBasePath) => {
const entryFilePath = path.join(logicBasePath, 'index.js');
const codes = await fs.readFile(entryFilePath, 'utf8');
const modifiedContent = codes
.replace(new RegExp(INSERT_IMPORT_PLUGINS_COMMENT), () => {
return plugins
.map((plugin, index) => `import plugin${index} from '${plugin}';`)
.join('\n');
return plugins.map((plugin, index) => `import plugin${index} from '${plugin}';`).join('\n');
})
.replace(new RegExp(INSERT_USE_PLUGINS_COMMENT), () => {
return plugins
.map((_, index) => `logic.use(plugin${index});`)
.join('\n');
return plugins.map((_, index) => `logic.use(plugin${index});`).join('\n');
});
await fs.outputFile(entryFilePath, modifiedContent);
};

15
packages/cli/src/cmd/dev/extractCodes.js

@ -12,20 +12,23 @@ const writeEntryFile = async (basePath, fileIds) => {
const fileContent = [
imports.join('\n'),
`const nodeFns = {\n ${funcMaps.join(',\n ')}\n};`,
`export default nodeFns;`
'export default nodeFns;',
].join('\n');
const entryFilePath = path.join(basePath, 'index.js');
await fs.outputFile(entryFilePath, fileContent, {encoding: 'utf8', flag:'w'});
await fs.outputFile(entryFilePath, fileContent, { encoding: 'utf8', flag: 'w' });
};
const writeNodeCodes = async (basePath, dsl) => {
const fileIds = [];
const {cells = []} = dsl;
const nodes = cells.filter(cell => cell.shape !== 'edge');
for(const {id, data: {code}} of nodes) {
const { cells = [] } = dsl;
const nodes = cells.filter((cell) => cell.shape !== 'edge');
for (const {
id,
data: { code },
} of nodes) {
fileIds.push(id);
const filePath = path.join(basePath, id + '.js');
await fs.outputFile(filePath, code, {encoding: 'utf8', flag:'w'});
await fs.outputFile(filePath, code, { encoding: 'utf8', flag: 'w' });
}
return fileIds;
};

24
packages/cli/src/cmd/dev/index.js

@ -5,7 +5,7 @@ const mergePkg = require('./mergePkg');
const addPlugins = require('./addPlugins');
const simplifyDSL = require('./simplifyDSL');
const extractCodes = require('./extractCodes');
const {createServer} = require('../../utils/server');
const { createServer } = require('../../utils/server');
const noop = () => {};
const TPL_PATH = path.join(__dirname, './template');
@ -13,41 +13,39 @@ const CACHE_PATH = path.join(process.cwd(), './.cache');
const CACHE_DSL_FILE = path.join(CACHE_PATH, 'imove.dsl.json');
class Dev extends Base {
save = async (req, res) => {
const {outputPath, plugins = []} = this.config;
async save(req, res) {
const { outputPath, plugins = [] } = this.config;
// check outputPath whether exsited
await fs.ensureDir(outputPath);
// check dsl whether existed
if (!req.body || !req.body.dsl) {
res.status(500).json({isCompiled: false}).end();
res.status(500).json({ isCompiled: false }).end();
return;
}
// compile
try {
const {dsl} = req.body;
const { dsl } = req.body;
await simplifyDSL(dsl, outputPath);
await extractCodes(dsl, outputPath);
await fs.copy(TPL_PATH, outputPath);
await addPlugins(plugins, outputPath);
await mergePkg(dsl, this.projectPath);
await fs.outputFile(CACHE_DSL_FILE, JSON.stringify(dsl, null, 2));
res.status(200).json({isCompiled: true}).end();
res.status(200).json({ isCompiled: true }).end();
console.log('compile successfully!');
} catch(err) {
res.status(500).json({isCompiled: false}).end();
} catch (err) {
res.status(500).json({ isCompiled: false }).end();
console.log('compile failed! the error is:', err);
}
}
connect = async (req, res) => {
const {projectName} = this.config;
async connect(req, res) {
const { projectName } = this.config;
const dsl = await fs.readJson(CACHE_DSL_FILE).catch(noop);
res.status(200).json({projectName, dsl}).end();
res.status(200).json({ projectName, dsl }).end();
}
run() {

24
packages/cli/src/cmd/dev/mergePkg.js

@ -2,36 +2,38 @@ const path = require('path');
const fs = require('fs-extra');
const builtinDependencies = {
"eventemitter3": "^4.0.7"
eventemitter3: '^4.0.7',
};
const extractDep = (dsl) => {
const mergedDependencies = {};
const {cells = []} = dsl;
cells.filter(cell => cell.shape !== 'edge').forEach(cell => {
const {dependencies} = cell.data || {};
const { cells = [] } = dsl;
cells
.filter((cell) => cell.shape !== 'edge')
.forEach((cell) => {
const { dependencies } = cell.data || {};
try {
const json = JSON.parse(dependencies);
Object.keys(json).forEach(key => mergedDependencies[key] = json[key]);
} catch(err) {
console.log('extract dependencies failed, the error is:', err.message);
Object.keys(json).forEach((key) => (mergedDependencies[key] = json[key]));
} catch (error) {
console.log('extract dependencies failed, the error is:', error.message);
}
});
return mergedDependencies;
}
};
const setup = async (dsl, projectRootPath) => {
const pkgPath = path.join(projectRootPath, './package.json');
const pkgFile = await fs.readFile(pkgPath);
const pkgJson = JSON.parse(pkgFile);
const dslDependencies = extractDep(dsl);
if(!pkgJson.dependencies) {
if (!pkgJson.dependencies) {
pkgJson.dependencies = dslDependencies;
} else {
Object.keys(dslDependencies).forEach(key => {
Object.keys(dslDependencies).forEach((key) => {
pkgJson.dependencies[key] = dslDependencies[key];
});
Object.keys(builtinDependencies).forEach(key => {
Object.keys(builtinDependencies).forEach((key) => {
pkgJson.dependencies[key] = builtinDependencies[key];
});
}

10
packages/cli/src/cmd/dev/simplifyDSL.js

@ -3,20 +3,20 @@ const fs = require('fs-extra');
const extractObj = (obj = {}, keys = []) => {
const ret = {};
keys.forEach(key => ret[key] = obj[key]);
keys.forEach((key) => (ret[key] = obj[key]));
return ret;
};
const simplify = (dsl) => {
const {cells = []} = dsl;
const { cells = [] } = dsl;
return {
cells: cells.map(cell => {
if(cell.shape === 'edge') {
cells: cells.map((cell) => {
if (cell.shape === 'edge') {
return extractObj(cell, ['id', 'shape', 'source', 'target']);
} else {
return extractObj(cell, ['id', 'shape', 'data']);
}
})
}),
};
};

7
packages/cli/src/cmd/dev/template/context.js

@ -1,14 +1,13 @@
export default class Context {
constructor(opts) {
this._init(opts);
}
_init(opts = {}) {
const {payload = {}} = opts;
const { payload = {} } = opts;
this.curNode = null;
this.context = Object.create(null);
this.payload = Object.freeze({...payload});
this.payload = Object.freeze({ ...payload });
}
_transitTo(node, lastRet) {
@ -38,7 +37,7 @@ export default class Context {
}
setContext(data = {}) {
Object.keys(data).forEach(key => {
Object.keys(data).forEach((key) => {
this.context[key] = data[key];
});
}

2
packages/cli/src/cmd/dev/template/index.js

@ -2,7 +2,7 @@ import Logic from './logic';
import dsl from './dsl.json';
// import plugins here
const logic = new Logic({dsl});
const logic = new Logic({ dsl });
// use plugins here

74
packages/cli/src/cmd/dev/template/logic.js

@ -2,17 +2,14 @@ import nodeFns from './nodeFns';
import Context from './context';
import EventEmitter from 'eventemitter3';
const LIFECYCLE = [
'ctxCreated'
];
const LIFECYCLE = new Set(['ctxCreated']);
const SHAPES = {
START: 'imove-start',
BRANCH: 'imove-branch',
BEHAVIOR: 'imove-behavior'
}
export default class Logic extends EventEmitter{
BEHAVIOR: 'imove-behavior',
};
export default class Logic extends EventEmitter {
constructor(opts = {}) {
super();
this.dsl = opts.dsl;
@ -24,23 +21,23 @@ export default class Logic extends EventEmitter{
}
get nodes() {
return this.cells.filter(cell => cell.shape !== 'edge');
return this.cells.filter((cell) => cell.shape !== 'edge');
}
get startNodes() {
return this.cells.filter(cell => cell.shape === SHAPES.START);
return this.cells.filter((cell) => cell.shape === SHAPES.START);
}
get edges() {
return this.cells.filter(cell => cell.shape === 'edge');
return this.cells.filter((cell) => cell.shape === 'edge');
}
_runLifecycleEvent(eventName, ctx) {
if(LIFECYCLE.indexOf(eventName) === -1) {
if (!LIFECYCLE.has(eventName)) {
return console.warn(`Lifecycle ${eventName} is not supported!`);
}
if(this.lifeCycleEvents[eventName]) {
this.lifeCycleEvents[eventName].forEach(fn => fn(ctx));
if (this.lifeCycleEvents[eventName]) {
this.lifeCycleEvents[eventName].forEach((fn) => fn(ctx));
}
}
@ -52,8 +49,8 @@ export default class Logic extends EventEmitter{
}
_getStartNode(trigger) {
for(const cell of this.startNodes) {
if(cell.data.trigger === trigger) {
for (const cell of this.startNodes) {
if (cell.data.trigger === trigger) {
return cell;
}
}
@ -61,25 +58,28 @@ export default class Logic extends EventEmitter{
_getNextNodes(ctx, curNode, curRet) {
const nodes = [];
for(const edge of this.edges) {
for (const edge of this.edges) {
let isMatched = edge.source.cell === curNode.id;
// NOTE: if it is a imove-branch node, each port's condition should be tested whether it is matched
if(curNode.shape === SHAPES.BRANCH) {
if (curNode.shape === SHAPES.BRANCH) {
let matchedPort = '';
const {ports} = curNode.data;
for(const key in ports) {
const {condition} = ports[key];
const ret = new Function('ctx', 'return ' + condition)(ctx);
if(ret === Boolean(curRet)) {
const { ports } = curNode.data;
for (const key in ports) {
const { condition } = ports[key];
const ret = ((ctx) => {
return condition;
})(ctx);
// const ret = new Function('ctx', 'return ' + condition)(ctx);
if (ret === Boolean(curRet)) {
matchedPort = key;
break;
}
}
isMatched = isMatched && edge.source.port === matchedPort;
}
if(isMatched) {
if (isMatched) {
// NOTE: not each edge both has source and target
const nextNode = this.nodes.find(item => item.id === edge.target.cell);
const nextNode = this.nodes.find((item) => item.id === edge.target.cell);
nextNode && nodes.push(nextNode);
}
}
@ -87,24 +87,24 @@ export default class Logic extends EventEmitter{
}
use(pluginCreator) {
if(typeof pluginCreator !== 'function') {
if (typeof pluginCreator !== 'function') {
console.error('imove plugin must be a function.');
return;
}
const plugin = pluginCreator(this);
if(typeof plugin !== 'object' || plugin === null) {
if (typeof plugin !== 'object' || plugin === null) {
console.error('imove plugin must return an object.');
return;
}
for (let eventName in plugin) {
if(!plugin.hasOwnProperty(eventName)) {
for (const eventName in plugin) {
if (!Object.prototype.hasOwnProperty.call(plugin, eventName)) {
continue;
}
if(LIFECYCLE.indexOf(eventName) === -1) {
if (!LIFECYCLE.has(eventName)) {
console.warn(`Lifecycle ${eventName} is not supported in imove.`);
continue;
}
if(!this.lifeCycleEvents[eventName]) {
if (!this.lifeCycleEvents[eventName]) {
this.lifeCycleEvents[eventName] = [];
}
this.lifeCycleEvents[eventName].push(plugin[eventName]);
@ -115,23 +115,23 @@ export default class Logic extends EventEmitter{
ctx._transitTo(curNode, lastRet);
const fn = nodeFns[curNode.id];
const curRet = await fn(ctx);
if(curNode.shape !== SHAPES.BRANCH) {
if (curNode.shape !== SHAPES.BRANCH) {
lastRet = curRet;
}
const nextNodes = this._getNextNodes(ctx, curNode, curRet);
if(nextNodes.length > 0) {
nextNodes.forEach(async node => {
if (nextNodes.length > 0) {
nextNodes.forEach(async (node) => {
await this._execNode(ctx, node, lastRet);
});
}
}
async invoke(trigger, data) {
let curNode = this._getStartNode(trigger);
if(!curNode) {
return Promise.reject(`Invoke failed! No logic-start named ${trigger} found!`);
const curNode = this._getStartNode(trigger);
if (!curNode) {
return Promise.reject(new Error(`Invoke failed! No logic-start named ${trigger} found!`));
}
const ctx = this._createCtx({payload: data});
const ctx = this._createCtx({ payload: data });
await this._execNode(ctx, curNode);
}
}

2
packages/cli/src/cmd/index.js

@ -3,5 +3,5 @@ const Init = require('./init');
module.exports = {
dev: Dev,
init: Init
init: Init,
};

1
packages/cli/src/cmd/init/index.js

@ -6,7 +6,6 @@ const CONFIG_TPL_PATH = path.join(__dirname, './template/imove.config.js.tpl');
const OUTPUT_FILE_PATH = path.join(process.cwd(), 'imove.config.js');
class Init extends Base {
getProjectName() {
const pkgPath = path.join(this.projectPath, 'package.json');
const pkgJson = fs.readJSONSync(pkgPath);

6
packages/cli/src/utils/getConfig.js

@ -4,16 +4,16 @@ const fs = require('fs');
const CONFIG_FILE_NAME = 'imove.config.js';
const CONFIG_FILE_PATH = path.join(process.cwd(), CONFIG_FILE_NAME);
const DEFAULT_CONFIG = {
outputPath: path.join(process.cwd(), './src/logic/')
outputPath: path.join(process.cwd(), './src/logic/'),
};
const mergeConfig = (config, DEFAULT_CONFIG) => {
// TODO: merge config
return config;
}
};
const getConfig = () => {
const isConfigFileExisted = fs.existsSync(CONFIG_FILE_PATH);
if(isConfigFileExisted) {
if (isConfigFileExisted) {
return mergeConfig(require(CONFIG_FILE_PATH), DEFAULT_CONFIG);
} else {
return DEFAULT_CONFIG;

10
packages/cli/src/utils/server/index.js

@ -1,15 +1,13 @@
const http = require('http');
const cors = require('cors');
const express = require('express');
const bodyParser = require('body-parser');
const createServer = (port = 3500) => {
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.urlencoded({ extended: false }));
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
app.listen(port, () => {
@ -19,5 +17,5 @@ const createServer = (port = 3500) => {
};
module.exports = {
createServer
createServer,
};

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

@ -1,5 +1,5 @@
import axios from 'axios';
import { message} from 'antd';
import { message } from 'antd';
export interface ILocalConfig {
ip: string;
@ -9,7 +9,7 @@ export interface ILocalConfig {
export enum ActionType {
create = 'create',
update = 'update',
remove = 'remove'
remove = 'remove',
}
export interface IModifyGraphAction {
@ -21,16 +21,16 @@ export interface IModifyGraphAction {
interface RequestConfig {
url: string;
method?: 'get' | 'post';
params?: {[key: string]: any};
headers?: {[key: string]: string};
params?: { [key: string]: any };
headers?: { [key: string]: string };
}
const request = (function() {
const request = (function () {
const instance = axios.create();
instance.interceptors.response.use((response: any) => {
const {data} = response || {};
const {success, msg} = data || {};
if(success) {
const { data } = response || {};
const { success, msg } = data || {};
if (success) {
return data;
} else {
message.error(msg);
@ -38,60 +38,60 @@ const request = (function() {
}
});
return (config: RequestConfig) => {
const {url, method = 'post', params, headers = {}} = config;
const { url, method = 'post', params, headers = {} } = config;
return instance.request({
url,
method,
headers: {
'Content-Type': 'application/json;charset=utf-8',
...headers
...headers,
},
data: params,
timeout: 3000
timeout: 3000,
});
};
})();
export const localConfig: ILocalConfig = {
ip: '127.0.0.1',
port: '3500'
}
port: '3500',
};
export const updateLocalConfig = (config: ILocalConfig) => {
localConfig.ip = config.ip || localConfig.ip;
localConfig.port = config.port || localConfig.port;
}
};
export const localConnect = () => {
return fetch(`http://${localConfig.ip}:${localConfig.port}/api/connect`, {
method: 'GET',
headers: {'content-type': 'application/json'}
headers: { 'content-type': 'application/json' },
});
}
};
export const localSave = (data: any) => {
fetch(`http://${localConfig.ip}:${localConfig.port}/api/save`, {
method: 'POST',
headers: {'content-type': 'application/json'},
body: JSON.stringify(data)
headers: { 'content-type': 'application/json' },
body: JSON.stringify(data),
});
}
};
export const queryGraph = (projectId: string) => {
return request({
url: '/api/queryGraph',
params: {
projectId
}
projectId,
},
});
}
};
export const modifyGraph = (projectId: string, actions: IModifyGraphAction[]) => {
return request({
url: '/api/modifyGraph',
params: {
projectId,
actions
}
actions,
},
});
}
};

32
packages/core/src/common/baseCell/behavior.ts

@ -1,4 +1,4 @@
import {Shape} from '@antv/x6';
import { Shape } from '@antv/x6';
const schema = {
base: Shape.Rect,
@ -9,11 +9,11 @@ const schema = {
attrs: {
body: {
fill: '#AACCF7',
stroke: '#5E9CEE'
stroke: '#5E9CEE',
},
label: {
fill: '#333'
}
fill: '#333',
},
},
ports: {
groups: {
@ -25,7 +25,7 @@ const schema = {
magnet: true,
stroke: '#666',
strokeWidth: 1,
fill: '#fff'
fill: '#fff',
},
},
},
@ -37,7 +37,7 @@ const schema = {
magnet: true,
stroke: '#666',
strokeWidth: 1,
fill: '#fff'
fill: '#fff',
},
},
},
@ -49,7 +49,7 @@ const schema = {
magnet: true,
stroke: '#666',
strokeWidth: 1,
fill: '#fff'
fill: '#fff',
},
},
},
@ -61,7 +61,7 @@ const schema = {
magnet: true,
stroke: '#666',
strokeWidth: 1,
fill: '#fff'
fill: '#fff',
},
},
},
@ -69,29 +69,29 @@ const schema = {
items: [
{
id: 'top',
group: 'top'
group: 'top',
},
{
id: 'right',
group: 'right'
group: 'right',
},
{
id: 'bottom',
group: 'bottom'
group: 'bottom',
},
{
id: 'left',
group: 'left'
}
]
group: 'left',
},
],
},
data: {
label: '处理',
configSchema: '{\n \n}',
configData: {},
dependencies: '{\n \n}',
code: 'export default async function(ctx) {\n \n}'
}
code: 'export default async function(ctx) {\n \n}',
},
};
export default schema;

54
packages/core/src/common/baseCell/branch.ts

@ -1,4 +1,4 @@
import {Shape} from '@antv/x6';
import { Shape } from '@antv/x6';
const schema = {
base: Shape.Polygon,
@ -10,7 +10,7 @@ const schema = {
strokeWidth: 2,
stroke: '#333',
fill: '#FFFC7C',
refPoints: '0,10 10,0 20,10 10,20'
refPoints: '0,10 10,0 20,10 10,20',
},
label: {
refX: 0.5,
@ -19,8 +19,8 @@ const schema = {
fill: '#333',
fontSize: 14,
textAnchor: 'middle',
textVerticalAnchor: 'middle'
}
textVerticalAnchor: 'middle',
},
},
ports: {
groups: {
@ -32,7 +32,7 @@ const schema = {
magnet: true,
stroke: '#666',
strokeWidth: 1,
fill: '#fff'
fill: '#fff',
},
},
},
@ -45,12 +45,12 @@ const schema = {
magnet: true,
stroke: '#666',
strokeWidth: 1,
fill: '#fff'
fill: '#fff',
},
},
label: {
position: 'right'
}
position: 'right',
},
},
bottom: {
zIndex: 999999999,
@ -61,12 +61,12 @@ const schema = {
magnet: true,
stroke: '#666',
strokeWidth: 1,
fill: '#fff'
fill: '#fff',
},
},
label: {
position: 'bottom'
}
position: 'bottom',
},
},
left: {
position: 'left',
@ -76,7 +76,7 @@ const schema = {
magnet: true,
stroke: '#666',
strokeWidth: 1,
fill: '#fff'
fill: '#fff',
},
},
},
@ -84,31 +84,31 @@ const schema = {
items: [
{
id: 'top',
group: 'top'
group: 'top',
},
{
id: 'right',
group: 'right',
attrs: {
text: {
text: '是'
}
}
text: '是',
},
},
},
{
id: 'bottom',
group: 'bottom',
attrs: {
text: {
text: '否'
}
}
text: '否',
},
},
},
{
id: 'left',
group: 'left'
}
]
group: 'left',
},
],
},
data: {
label: '判断',
@ -117,14 +117,14 @@ const schema = {
dependencies: '{\n \n}',
ports: {
right: {
condition: 'true'
condition: 'true',
},
bottom: {
condition: 'false'
}
condition: 'false',
},
},
code: 'export default async function(ctx) {\n return true;\n}',
},
code: 'export default async function(ctx) {\n return true;\n}'
}
};
export default schema;

4
packages/core/src/common/baseCell/index.ts

@ -2,10 +2,10 @@ import start from './start';
import branch from './branch';
import behavior from './behavior';
const cellSchemaMap: {[key: string]: any} = {
const cellSchemaMap: { [key: string]: any } = {
'imove-start': start,
'imove-branch': branch,
'imove-behavior': behavior
'imove-behavior': behavior,
};
export default cellSchemaMap;

32
packages/core/src/common/baseCell/start.ts

@ -1,4 +1,4 @@
import {Shape} from '@antv/x6';
import { Shape } from '@antv/x6';
const schema = {
base: Shape.Circle,
@ -10,11 +10,11 @@ const schema = {
body: {
fill: '#333',
strokeWidth: 1,
stroke: '#333'
stroke: '#333',
},
label: {
fill: '#FFF'
}
fill: '#FFF',
},
},
ports: {
groups: {
@ -26,7 +26,7 @@ const schema = {
magnet: true,
stroke: '#666',
strokeWidth: 1,
fill: '#fff'
fill: '#fff',
},
},
},
@ -38,7 +38,7 @@ const schema = {
magnet: true,
stroke: '#666',
strokeWidth: 1,
fill: '#fff'
fill: '#fff',
},
},
},
@ -50,7 +50,7 @@ const schema = {
magnet: true,
stroke: '#666',
strokeWidth: 1,
fill: '#fff'
fill: '#fff',
},
},
},
@ -62,7 +62,7 @@ const schema = {
magnet: true,
stroke: '#666',
strokeWidth: 1,
fill: '#fff'
fill: '#fff',
},
},
},
@ -70,21 +70,21 @@ const schema = {
items: [
{
id: 'top',
group: 'top'
group: 'top',
},
{
id: 'right',
group: 'right'
group: 'right',
},
{
id: 'bottom',
group: 'bottom'
group: 'bottom',
},
{
id: 'left',
group: 'left'
}
]
group: 'left',
},
],
},
data: {
label: '开始',
@ -92,8 +92,8 @@ const schema = {
configData: {},
trigger: 'start',
dependencies: '{\n \n}',
code: 'export default async function(ctx) {\n \n}'
}
code: 'export default async function(ctx) {\n \n}',
},
};
export default schema;

10
packages/core/src/common/previewCell/behavior.ts

@ -1,4 +1,4 @@
import {Shape} from '@antv/x6';
import { Shape } from '@antv/x6';
const schema = {
base: Shape.Rect,
@ -10,12 +10,12 @@ const schema = {
attrs: {
body: {
fill: '#AACCF7',
stroke: '#5E9CEE'
stroke: '#5E9CEE',
},
label: {
fill: '#333'
}
}
fill: '#333',
},
},
};
export default schema;

10
packages/core/src/common/previewCell/branch.ts

@ -1,4 +1,4 @@
import {Shape} from '@antv/x6';
import { Shape } from '@antv/x6';
const schema = {
base: Shape.Polygon,
@ -12,7 +12,7 @@ const schema = {
strokeWidth: 2,
stroke: '#333',
fill: '#FFFC7C',
refPoints: '0,10 10,0 20,10 10,20'
refPoints: '0,10 10,0 20,10 10,20',
},
label: {
refX: 0.5,
@ -21,9 +21,9 @@ const schema = {
fill: '#333',
fontSize: 14,
textAnchor: 'middle',
textVerticalAnchor: 'middle'
}
}
textVerticalAnchor: 'middle',
},
},
};
export default schema;

4
packages/core/src/common/previewCell/index.ts

@ -2,10 +2,10 @@ import start from './start';
import branch from './branch';
import behavior from './behavior';
const cellSchemaMap: {[key: string]: any} = {
const cellSchemaMap: { [key: string]: any } = {
'imove-start-preview': start,
'imove-branch-preview': branch,
'imove-behavior-preview': behavior
'imove-behavior-preview': behavior,
};
export default cellSchemaMap;

8
packages/core/src/common/previewCell/start.ts

@ -1,4 +1,4 @@
import {Shape} from '@antv/x6';
import { Shape } from '@antv/x6';
const schema = {
base: Shape.Circle,
@ -11,11 +11,11 @@ const schema = {
body: {
strokeWidth: 0,
stroke: '#333',
fill: '#333'
fill: '#333',
},
label: {
fill: '#FFF'
}
fill: '#FFF',
},
},
};

75
packages/core/src/common/shortcuts.ts

@ -1,50 +1,50 @@
import {safeGet} from '../utils';
import {localSave} from '../api';
import {Cell, Edge, Graph, Node} from '@antv/x6';
import {MIN_ZOOM, MAX_ZOOM, ZOOM_STEP} from './const';
import { safeGet } from '../utils';
import { localSave } from '../api';
import { Cell, Edge, Graph, Node } from '@antv/x6';
import { MIN_ZOOM, MAX_ZOOM, ZOOM_STEP } from './const';
interface Shortcut {
keys: string | string[];
handler: (flowChart: Graph) => void;
}
const shortcuts: {[key: string]: Shortcut} = {
const shortcuts: { [key: string]: Shortcut } = {
save: {
keys: `meta + s`,
keys: 'meta + s',
handler(flowChart: Graph) {
localSave({dsl: flowChart.toJSON()});
localSave({ dsl: flowChart.toJSON() });
return false;
}
},
},
undo: {
keys: `meta + z`,
keys: 'meta + z',
handler(flowChart: Graph) {
flowChart.undo();
return false;
}
},
},
redo: {
keys: `meta + shift + z`,
keys: 'meta + shift + z',
handler(flowChart: Graph) {
flowChart.redo();
return false;
}
},
},
zoomIn: {
keys: `meta + shift + \+`,
keys: 'meta + shift + +',
handler(flowChart: Graph) {
const nextZoom = (flowChart.zoom() + ZOOM_STEP).toPrecision(2);
flowChart.zoomTo(Number(nextZoom), {maxScale: MAX_ZOOM});
flowChart.zoomTo(Number(nextZoom), { maxScale: MAX_ZOOM });
return false;
}
},
},
zoomOut: {
keys: `meta + shift + \-`,
keys: 'meta + shift + -',
handler(flowChart: Graph) {
const nextZoom = (flowChart.zoom() - ZOOM_STEP).toPrecision(2);
flowChart.zoomTo(Number(nextZoom), {minScale: MIN_ZOOM});
flowChart.zoomTo(Number(nextZoom), { minScale: MIN_ZOOM });
return false;
}
},
},
copy: {
keys: 'meta + c',
@ -54,18 +54,18 @@ const shortcuts: {[key: string]: Shortcut} = {
flowChart.copy(cells);
}
return false;
}
},
},
paste: {
keys: 'meta + v',
handler(flowChart: Graph) {
if (!flowChart.isClipboardEmpty()) {
const cells = flowChart.paste({offset: 32});
const cells = flowChart.paste({ offset: 32 });
flowChart.cleanSelection();
flowChart.select(cells);
}
return false;
}
},
},
delete: {
keys: ['backspace', 'del'],
@ -73,17 +73,17 @@ const shortcuts: {[key: string]: Shortcut} = {
const toDelCells = flowChart.getSelectedCells();
const onEdgeDel = (edge: Edge) => {
const srcNode = edge.getSourceNode() as Node;
const isSrcNodeInDelCells = !!toDelCells.find(c => c === srcNode);
if(srcNode && srcNode.shape === 'imove-branch' && !isSrcNodeInDelCells) {
const isSrcNodeInDelCells = !!toDelCells.find((c) => c === srcNode);
if (srcNode && srcNode.shape === 'imove-branch' && !isSrcNodeInDelCells) {
const portId = edge.getSourcePortId();
if(portId === 'right' || portId === 'bottom') {
if (portId === 'right' || portId === 'bottom') {
const edgeLabel = safeGet(edge.getLabelAt(0), 'attrs.label.text', '');
srcNode.setPortProp(portId, 'attrs/text/text', edgeLabel);
}
}
};
toDelCells.forEach((cell: Cell) => {
if(cell.isEdge()) {
if (cell.isEdge()) {
onEdgeDel(cell);
} else {
flowChart.getConnectedEdges(cell).forEach(onEdgeDel);
@ -92,14 +92,14 @@ const shortcuts: {[key: string]: Shortcut} = {
flowChart.removeCells(flowChart.getSelectedCells());
flowChart.trigger('toolBar:forceUpdate');
return false;
}
},
},
selectAll: {
keys: 'meta + a',
handler(flowChart: Graph) {
flowChart.select(flowChart.getCells());
return false;
}
},
},
bold: {
keys: 'meta + b',
@ -108,41 +108,42 @@ const shortcuts: {[key: string]: Shortcut} = {
if (cells.length > 0) {
const isAlreadyBold = safeGet(cells, '0.attrs.label.fontWeight', 'normal') === 'bold';
cells.forEach((cell) => {
cell.setAttrs({label: {fontWeight: isAlreadyBold ? 'normal' : 'bold'}});
cell.setAttrs({ label: { fontWeight: isAlreadyBold ? 'normal' : 'bold' } });
});
flowChart.trigger('toolBar:forceUpdate');
}
return false;
}
},
},
italic: {
keys: 'meta + i',
handler(flowChart: Graph) {
const cells = flowChart.getSelectedCells();
if(cells.length > 0) {
if (cells.length > 0) {
const isAlreadyItalic = safeGet(cells, '0.attrs.label.fontStyle', 'normal') === 'italic';
cells.forEach((cell) => {
cell.setAttrs({label: {fontStyle: isAlreadyItalic ? 'normal' : 'italic'}});
cell.setAttrs({ label: { fontStyle: isAlreadyItalic ? 'normal' : 'italic' } });
});
flowChart.trigger('toolBar:forceUpdate');
}
return false;
}
},
},
underline: {
keys: 'meta + u',
handler(flowChart: Graph) {
const cells = flowChart.getSelectedCells();
if(cells.length > 0) {
const isAlreadyUnderline = safeGet(cells, '0.attrs.label.textDecoration', 'none') === 'underline';
if (cells.length > 0) {
const isAlreadyUnderline =
safeGet(cells, '0.attrs.label.textDecoration', 'none') === 'underline';
cells.forEach((cell) => {
cell.setAttrs({label: {textDecoration: isAlreadyUnderline ? 'none' : 'underline'}});
cell.setAttrs({ label: { textDecoration: isAlreadyUnderline ? 'none' : 'underline' } });
});
flowChart.trigger('toolBar:forceUpdate');
}
return false;
}
}
},
},
};
export default shortcuts;

15
packages/core/src/components/colorPicker/index.tsx

@ -1,6 +1,6 @@
import React, {useState, useEffect} from 'react';
import React, { useState, useEffect } from 'react';
import {SketchPicker, ColorResult} from 'react-color';
import { SketchPicker, ColorResult } from 'react-color';
interface IProps {
color: string;
@ -8,8 +8,7 @@ interface IProps {
}
const ColorPicker: React.FC<IProps> = (props) => {
const {color, onChangeComplete: changeCb} = props;
const { color, onChangeComplete: changeCb } = props;
const [curColor, setCurColor] = useState(color);
// life
@ -19,13 +18,7 @@ const ColorPicker: React.FC<IProps> = (props) => {
const onChange = (color: ColorResult): void => setCurColor(color.hex);
const onChangeComplete = (color: ColorResult): void => changeCb(color.hex);
return (
<SketchPicker
color={curColor}
onChange={onChange}
onChangeComplete={onChangeComplete}
/>
);
return <SketchPicker color={curColor} onChange={onChange} onChangeComplete={onChangeComplete} />;
};
export default ColorPicker;

5
packages/core/src/components/miniMapSimpleNode/index.tsx

@ -1,7 +1,6 @@
import {NodeView} from '@antv/x6';
import { NodeView } from '@antv/x6';
class MiniMapSimpleNode extends NodeView {
protected renderMarkup() {
return this.renderJSONMarkup({
tagName: 'rect',
@ -19,7 +18,7 @@ class MiniMapSimpleNode extends NodeView {
refWidth: '100%',
refHeight: '100%',
fill: '#D9D9D9',
}
},
});
}
}

4
packages/core/src/components/xIcon/index.tsx

@ -1,5 +1,5 @@
import React from 'react';
import {createFromIconfontCN} from '@ant-design/icons';
import { createFromIconfontCN } from '@ant-design/icons';
interface IconFontProps {
type: string;
@ -7,7 +7,7 @@ interface IconFontProps {
}
const XIcon: React.SFC<IconFontProps> = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_2024452_02tul6sizoy9.js'
scriptUrl: '//at.alicdn.com/t/font_2024452_02tul6sizoy9.js',
});
export default XIcon;

12
packages/core/src/index.tsx

@ -1,9 +1,9 @@
import React, {useState} from 'react';
import React, { useState } from 'react';
import './global.index.less';
import '@antv/x6/dist/x6.css';
import {Graph} from '@antv/x6';
import { Graph } from '@antv/x6';
import Layout from './mods/layout';
import Header from './mods/header';
import SideBar from './mods/sideBar';
@ -12,11 +12,11 @@ import FlowChart from './mods/flowChart';
import SettingBar from './mods/settingBar';
interface IProps {
onSave: (data: {nodes: any; edges: any}) => void;
onSave: (data: { nodes: any; edges: any }) => void;
}
const Core: React.FC<IProps> = props => {
const {onSave} = props;
const Core: React.FC<IProps> = (props) => {
const { onSave } = props;
const [flowChart, setFlowChart] = useState<Graph>();
const onFlowChartReady = (flowChart: Graph): void => setFlowChart(flowChart);
return (
@ -27,7 +27,7 @@ const Core: React.FC<IProps> = props => {
ToolBar={ToolBar}
SettingBar={SettingBar}
>
<FlowChart onReady={onFlowChartReady}/>
<FlowChart onReady={onFlowChartReady} />
</Layout>
);
};

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

@ -1,17 +1,18 @@
import shortcuts from '../../common/shortcuts';
import {Cell, Edge, Graph, Node} from '@antv/x6';
import {MIN_ZOOM, MAX_ZOOM} from '../../common/const';
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 { registerServerStorage } from './registerServerStorage';
import MiniMapSimpleNode from '../../components/miniMapSimpleNode';
// X6 register base/preview cell shape
[baseCellSchemaMap, previewCellSchemaMap]
.forEach(schemas => Object.values(schemas).forEach(schema => {
const {base, ...rest} = schema;
[baseCellSchemaMap, previewCellSchemaMap].forEach((schemas) =>
Object.values(schemas).forEach((schema) => {
const { base, ...rest } = schema;
base.define(rest);
}));
})
);
const registerEvents = (flowChart: Graph): void => {
flowChart.on('node:added', (args) => {
@ -25,9 +26,9 @@ const registerEvents = (flowChart: Graph): void => {
flowChart.on('edge:connected', (args) => {
const edge = args.edge as Edge;
const sourceNode = edge.getSourceNode() as Node;
if(sourceNode && sourceNode.shape === 'imove-branch') {
if (sourceNode && sourceNode.shape === 'imove-branch') {
const portId = edge.getSourcePortId();
if(portId === 'right' || portId === 'bottom') {
if (portId === 'right' || portId === 'bottom') {
edge.setLabelAt(0, sourceNode.getPortProp(portId, 'attrs/text/text'));
sourceNode.setPortProp(portId, 'attrs/text/text', '');
}
@ -36,8 +37,8 @@ const registerEvents = (flowChart: Graph): void => {
};
const registerShortcuts = (flowChart: Graph): void => {
Object.values(shortcuts).forEach(shortcut => {
const {keys, handler} = shortcut;
Object.values(shortcuts).forEach((shortcut) => {
const { keys, handler } = shortcut;
flowChart.bindKey(keys, () => handler(flowChart));
});
};
@ -50,7 +51,7 @@ const createFlowChart = (container: HTMLDivElement, miniMapContainer: HTMLDivEle
// https://x6.antv.vision/zh/docs/tutorial/basic/clipboard
clipboard: {
enabled: true,
useLocalStorage: true
useLocalStorage: true,
},
// https://x6.antv.vision/zh/docs/tutorial/intermediate/connector
connecting: {
@ -60,25 +61,25 @@ const createFlowChart = (container: HTMLDivElement, miniMapContainer: HTMLDivEle
anchor: 'center',
connectionPoint: 'anchor',
router: {
name: 'manhattan'
name: 'manhattan',
},
validateConnection({sourceView, targetView, sourceMagnet, targetMagnet}) {
if(!sourceMagnet) {
validateConnection({ sourceView, targetView, sourceMagnet, targetMagnet }) {
if (!sourceMagnet) {
return false;
} else if(!targetMagnet) {
} else if (!targetMagnet) {
return false;
} else {
return sourceView !== targetView;
}
}
},
},
// https://x6.antv.vision/zh/docs/tutorial/basic/background
background: {
color: '#f8f9fa'
color: '#f8f9fa',
},
// https://x6.antv.vision/zh/docs/tutorial/basic/grid
grid: {
visible: true
visible: true,
},
// https://x6.antv.vision/zh/docs/tutorial/basic/selection
selecting: {
@ -86,25 +87,25 @@ const createFlowChart = (container: HTMLDivElement, miniMapContainer: HTMLDivEle
multiple: true,
rubberband: true,
movable: true,
showNodeSelectionBox: true
showNodeSelectionBox: true,
},
// https://x6.antv.vision/zh/docs/tutorial/basic/snapline
snapline: {
enabled: true,
clean: 100
clean: 100,
},
// https://x6.antv.vision/zh/docs/tutorial/basic/keyboard
keyboard: {
enabled: true,
global: false
global: false,
},
// https://x6.antv.vision/zh/docs/tutorial/basic/history
history: {
enabled: true
enabled: true,
},
// https://x6.antv.vision/zh/docs/tutorial/basic/minimap
minimap: {
width: 150 * container.offsetWidth / container.offsetHeight,
width: (150 * container.offsetWidth) / container.offsetHeight,
height: 150,
minScale: MIN_ZOOM,
maxScale: MAX_ZOOM,
@ -114,7 +115,7 @@ const createFlowChart = (container: HTMLDivElement, miniMapContainer: HTMLDivEle
graphOptions: {
async: true,
getCellView(cell: Cell) {
if(cell.isNode()){
if (cell.isNode()) {
return MiniMapSimpleNode;
}
},
@ -122,18 +123,18 @@ const createFlowChart = (container: HTMLDivElement, miniMapContainer: HTMLDivEle
if (cell.isEdge()) {
return null;
}
}
}
},
},
},
// https://x6.antv.vision/zh/docs/tutorial/basic/scroller
scroller: {
enabled: true
enabled: true,
},
mousewheel: {
enabled: true,
minScale: MIN_ZOOM,
maxScale: MAX_ZOOM,
modifiers: ['ctrl', 'meta']
modifiers: ['ctrl', 'meta'],
},
});
registerEvents(flowChart);

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

@ -12,9 +12,8 @@ interface IProps {
onReady: (graph: Graph) => void;
}
const FlowChart: React.FC<IProps> = props => {
const {onReady} = props;
const FlowChart: React.FC<IProps> = (props) => {
const { onReady } = props;
const graphRef = useRef<HTMLDivElement>(null);
const miniMapRef = useRef<HTMLDivElement>(null);
@ -28,18 +27,20 @@ const FlowChart: React.FC<IProps> = props => {
const fetchData = (graph: Graph) => {
const { projectId } = parseQuery();
queryGraph(projectId as string).then(res => {
queryGraph(projectId as string)
.then((res) => {
const { data: dsl } = res;
graph.fromJSON(dsl);
}).catch(err => {
console.log('query graph data failed, the error is:', err);
})
.catch((error) => {
console.log('query graph data failed, the error is:', error);
});
};
return (
<div className={styles.container}>
<div className={styles.flowChart} ref={graphRef}/>
<div className={styles.miniMap} ref={miniMapRef}/>
<div className={styles.flowChart} ref={graphRef} />
<div className={styles.miniMap} ref={miniMapRef} />
</div>
);
};

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

@ -15,15 +15,16 @@ const validate = (type: string, data: any) => {
} 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);
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);
@ -37,19 +38,21 @@ const save = (flowChart: Graph, type: string, actionType: ActionType, data: any)
clearTimeout(modifyActionTimer);
modifyActionTimer = setTimeout(() => {
const pushedActions = memQueue.slice(0);
if(pushedActions.length > 0) {
if (pushedActions.length > 0) {
flowChart.trigger('graph:change:modify');
modifyGraph(projectId, memQueue).then(res => {
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});
flowChart.trigger('graph:modified', { success: true });
})
.catch((error) => {
flowChart.trigger('graph:modified', { success: true, error: error });
});
}
}, 100);
};
type ActionEventMap = {[key: string]: string[]};
type ActionEventMap = { [key: string]: string[] };
const nodeActionEventMap: ActionEventMap = {
[ActionType.create]: ['node:added'],
[ActionType.remove]: ['node:removed'],
@ -60,23 +63,20 @@ const nodeActionEventMap: ActionEventMap = {
'node:change:ports',
'node:change:attrs',
'node:change:data',
'node:change:zIndex'
]
'node:change:zIndex',
],
};
const edgeActionEventMap: ActionEventMap = {
[ActionType.create]: ['edge:connected'],
[ActionType.remove]: ['edge:removed'],
[ActionType.update]: [
'edge:moved',
]
[ActionType.update]: ['edge:moved'],
};
export const registerServerStorage = (flowChart: Graph) => {
Object.keys(nodeActionEventMap).forEach((actionType) => {
const events = nodeActionEventMap[actionType];
events.forEach(event => {
events.forEach((event) => {
flowChart.on(event, (args: any) => {
console.log('node event:', event, 'args:', args);
save(flowChart, 'node', actionType as ActionType, args.node.toJSON());
@ -86,7 +86,7 @@ export const registerServerStorage = (flowChart: Graph) => {
Object.keys(edgeActionEventMap).forEach((actionType) => {
const events = edgeActionEventMap[actionType];
events.forEach(event => {
events.forEach((event) => {
flowChart.on(event, (args: any) => {
console.log('edge event:', event, 'args:', args);
save(flowChart, 'edge', actionType as ActionType, args.edge.toJSON());

40
packages/core/src/mods/header/connectStatus/editModal.tsx

@ -1,22 +1,25 @@
import React, {useState, useEffect, useRef} from 'react';
import React, { useState, useEffect, useRef } from 'react';
import 'antd/es/form/style';
import 'antd/es/modal/style';
import 'antd/es/input/style';
import styles from './index.module.less';
import {Input, Form, Modal} from 'antd';
import {FormInstance} from 'antd/lib/form';
import {ILocalConfig, localConfig, updateLocalConfig} from '../../../api';
import { Input, Form, Modal } from 'antd';
import { FormInstance } from 'antd/lib/form';
import { ILocalConfig, localConfig, updateLocalConfig } from '../../../api';
const PORT_REGEX = /^\d{1,5}$/;
const IP_REGEX = /^(localhost)|(((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3})$/;
const IP_REGEX = /^(localhost)|(((2(5[0-5]|[0-4]\d))|[01]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[01]?\d{1,2})){3})$/;
const makeRules = (regex: RegExp, errTip: string) => {
return [
{required: true, message: '不能为空!'},
{validator: (_: any, val: string) => regex.test(val) ? Promise.resolve() : Promise.reject(errTip)}
{ required: true, message: '不能为空!' },
{
validator: (_: any, val: string) =>
regex.test(val) ? Promise.resolve() : Promise.reject(errTip),
},
];
}
};
interface IEditorModalProps {
visible: boolean;
@ -24,18 +27,21 @@ interface IEditorModalProps {
onCancel: () => void;
}
const EditModal: React.FC<IEditorModalProps> = props => {
const {visible, onOk, onCancel} = props;
const EditModal: React.FC<IEditorModalProps> = (props) => {
const { visible, onOk, onCancel } = props;
const formRef = useRef<FormInstance>(null);
// events
const onClickOk = (): void => {
formRef.current && formRef.current.validateFields().then((formValues) => {
formRef.current &&
formRef.current
.validateFields()
.then((formValues) => {
updateLocalConfig(formValues as ILocalConfig);
onOk();
}).catch(err => {
console.log(err.message);
})
.catch((error) => {
console.log(error.message);
});
};
@ -49,12 +55,12 @@ const EditModal: React.FC<IEditorModalProps> = props => {
onOk={onClickOk}
onCancel={onCancel}
>
<Form ref={formRef} labelCol={{span: 4}} initialValues={localConfig}>
<Form ref={formRef} labelCol={{ span: 4 }} initialValues={localConfig}>
<Form.Item label={'ip'} name={'ip'} rules={makeRules(IP_REGEX, 'IP格式不合法')}>
<Input/>
<Input />
</Form.Item>
<Form.Item label={'port'} name={'port'} rules={makeRules(PORT_REGEX, 'port格式不合法')}>
<Input/>
<Input />
</Form.Item>
</Form>
</Modal>

43
packages/core/src/mods/header/connectStatus/index.tsx

@ -1,18 +1,18 @@
import React, {useState, useEffect} from 'react';
import React, { useState, useEffect } from 'react';
import 'antd/es/tag/style';
import 'antd/es/modal/style';
import 'antd/es/message/style';
import styles from './index.module.less';
import {Graph} from '@antv/x6';
import { Graph } from '@antv/x6';
import EditModal from './editModal';
import {Tag, Modal, message} from 'antd';
import {localConnect} from '../../../api';
import { Tag, Modal, message } from 'antd';
import { localConnect } from '../../../api';
enum Status {
connected = 'success',
disconnected = 'error'
disconnected = 'error',
}
interface IProps {
@ -22,8 +22,7 @@ interface IProps {
const PULSE_RATE = 10 * 1000; // try to connect to local servet per 10 seconds
const ConnectStatus: React.FC<IProps> = (props) => {
const {flowChart} = props;
const { flowChart } = props;
const [visible, setVisible] = useState<boolean>(false);
const [projectName, setProjectName] = useState<string>('');
const [status, setStatus] = useState<Status>(Status.disconnected);
@ -37,28 +36,32 @@ const ConnectStatus: React.FC<IProps> = (props) => {
// network
const syncLocal = () => {
return localConnect().then(res => res.json()).then((data = {}) => {
const {projectName} = data;
return localConnect()
.then((res) => res.json())
.then((data = {}) => {
const { projectName } = data;
setStatus(Status.connected);
setProjectName(projectName);
return data;
}).catch((err) => {
})
.catch((error) => {
setStatus(Status.disconnected);
console.log('connect local failed, the error is:', err.message);
console.log('connect local failed, the error is:', error.message);
});
};
const confirmToSync = () => {
syncLocal().then(data => {
const {dsl} = data || {};
dsl && Modal.confirm({
syncLocal().then((data) => {
const { dsl } = data || {};
dsl &&
Modal.confirm({
title: '本地连接成功,是否将数据同步至当前项目?',
onOk() {
try {
flowChart.fromJSON(dsl);
} catch(err) {
} catch (error) {
message.error('同步失败!');
}
}
},
});
});
};
@ -76,7 +79,7 @@ const ConnectStatus: React.FC<IProps> = (props) => {
};
let tagText = '本地连接失败 点击修改配置';
if(status === Status.connected) {
if (status === Status.connected) {
tagText = [projectName, '本地连接成功'].join(' ');
}
@ -85,11 +88,7 @@ const ConnectStatus: React.FC<IProps> = (props) => {
<Tag className={styles.tag} color={status} onClick={onOpenEditModal}>
{tagText}
</Tag>
<EditModal
visible={visible}
onOk={onOk}
onCancel={onCloseEditModal}
/>
<EditModal visible={visible} onOk={onOk} onCancel={onCloseEditModal} />
</div>
);
};

10
packages/core/src/mods/header/index.tsx

@ -2,21 +2,21 @@ import React from 'react';
import styles from './index.module.less';
import {Graph} from '@antv/x6';
import { Graph } from '@antv/x6';
import ConnectStatus from './connectStatus';
interface IProps {
flowChart: Graph;
}
const Header: React.FC<IProps> = props => {
const {flowChart} = props;
const Header: React.FC<IProps> = (props) => {
const { flowChart } = props;
return (
<div className={styles.container}>
<span className={styles.titleText}>iMove</span>
<ConnectStatus flowChart={flowChart}/>
<ConnectStatus flowChart={flowChart} />
</div>
);
}
};
export default Header;

31
packages/core/src/mods/layout/index.tsx

@ -2,7 +2,7 @@ import React from 'react';
import styles from './index.module.less';
import {Graph} from '@antv/x6';
import { Graph } from '@antv/x6';
interface ISubComponentProps {
flowChart: Graph;
@ -17,35 +17,26 @@ interface IProps {
}
const Layout: React.FC<IProps> = (props) => {
const {flowChart, Header, SideBar, ToolBar, SettingBar} = props;
const { flowChart, Header, SideBar, ToolBar, SettingBar } = props;
let header, sideBar, toolBar, settingBar;
if(flowChart) {
header = <Header flowChart={flowChart}/>;
sideBar = <SideBar flowChart={flowChart}/>;
toolBar = <ToolBar flowChart={flowChart}/>;
settingBar = <SettingBar flowChart={flowChart}/>;
if (flowChart) {
header = <Header flowChart={flowChart} />;
sideBar = <SideBar flowChart={flowChart} />;
toolBar = <ToolBar flowChart={flowChart} />;
settingBar = <SettingBar flowChart={flowChart} />;
}
return (
<div className={styles.container}>
<div className={styles.header}>
{header}
</div>
<div className={styles.header}>{header}</div>
<div className={styles.body}>
<div className={styles.sideBar}>
{sideBar}
</div>
<div className={styles.sideBar}>{sideBar}</div>
<div className={styles.full}>
<div className={styles.toolBar}>
{toolBar}
</div>
<div className={styles.toolBar}>{toolBar}</div>
{props.children}
</div>
<div className={styles.settingBar}>
{settingBar}
</div>
<div className={styles.settingBar}>{settingBar}</div>
</div>
</div>
);

14
packages/core/src/mods/settingBar/components/checkbox/index.tsx

@ -3,8 +3,8 @@ import React from 'react';
import 'antd/es/checkbox/style';
import styles from './index.module.less';
import {Checkbox as AntdCheckbox} from 'antd';
import {CheckboxChangeEvent} from 'antd/lib/checkbox';
import { Checkbox as AntdCheckbox } from 'antd';
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
interface IProps {
value: any;
@ -15,18 +15,18 @@ interface IProps {
onValueChange: (val: boolean) => void;
}
const Checkbox: React.FC<IProps> = props => {
const {name, title, value, disabled, onValueChange} = props;
const Checkbox: React.FC<IProps> = (props) => {
const { name, title, value, disabled, onValueChange } = props;
const onChange = (evt: CheckboxChangeEvent): void => {
console.log(evt.target.checked)
if(evt.target.checked !== value) {
console.log(evt.target.checked);
if (evt.target.checked !== value) {
onValueChange(evt.target.checked);
}
};
return (
<div className={styles.container}>
<p className={styles.titleText}>{title}</p>
<AntdCheckbox name={name} checked={value} disabled={disabled} onChange={onChange}/>
<AntdCheckbox name={name} checked={value} disabled={disabled} onChange={onChange} />
</div>
);
};

28
packages/core/src/mods/settingBar/components/code/index.tsx

@ -1,11 +1,11 @@
import React, {useState, useEffect} from 'react';
import React, { useState, useEffect } from 'react';
import 'antd/es/modal/style';
import 'antd/es/button/style';
import styles from './index.module.less';
import AceEditor from 'react-ace';
import {Button, Modal} from 'antd';
import { Button, Modal } from 'antd';
import 'ace-builds/src-noconflict/theme-dracula';
import 'ace-builds/src-noconflict/mode-javascript';
import 'ace-builds/src-noconflict/snippets/javascript';
@ -18,9 +18,8 @@ interface IProps {
onValueChange: (value: string) => void;
}
const Code: React.FC<IProps> = props => {
const {title, value, onValueChange} = props;
const Code: React.FC<IProps> = (props) => {
const { title, value, onValueChange } = props;
const [visible, setVisible] = useState<boolean>(false);
// events
@ -38,11 +37,7 @@ const Code: React.FC<IProps> = props => {
return (
<div className={styles.container}>
<p className={styles.titleText}>{title}</p>
<Button
block={true}
className={styles.btn}
onClick={onClickEdit}
>
<Button block={true} className={styles.btn} onClick={onClickEdit}>
</Button>
<EditModal
@ -66,23 +61,22 @@ interface IEditorModalProps {
const CODE_EDITOR_STYLE = {
width: '100%',
height: 600
height: 600,
};
const CODE_EDITOR_OPTIONS = {
fontSize: 14,
useWorker: false
useWorker: false,
};
const EditModal: React.FC<IEditorModalProps> = props => {
const {visible, title, value, onOk, onCancel} = props;
const EditModal: React.FC<IEditorModalProps> = (props) => {
const { visible, title, value, onOk, onCancel } = props;
const [code, setCode] = useState<string>(value);
// life
useEffect(() => {
// set value when opening modal
// clear content when closing modal
if(visible) {
if (visible) {
setCode(value);
} else {
setCode('');
@ -94,7 +88,7 @@ const EditModal: React.FC<IEditorModalProps> = props => {
onOk(code);
};
const onChangeCode = (newCode: string): void => {
if(newCode !== code) {
if (newCode !== code) {
setCode(newCode);
}
};

10
packages/core/src/mods/settingBar/components/input/index.tsx

@ -3,7 +3,7 @@ import React from 'react';
import 'antd/es/input/style';
import styles from './index.module.less';
import {Input as AntdInput} from 'antd';
import { Input as AntdInput } from 'antd';
interface IProps {
value: any;
@ -14,17 +14,17 @@ interface IProps {
onValueChange: (val: string) => void;
}
const Input: React.FC<IProps> = props => {
const {name, title, value, disabled, onValueChange} = props;
const Input: React.FC<IProps> = (props) => {
const { name, title, value, disabled, onValueChange } = props;
const onChange = (evt: React.ChangeEvent<HTMLInputElement>): void => {
if(evt.target.value !== value) {
if (evt.target.value !== value) {
onValueChange(evt.target.value);
}
};
return (
<div className={styles.container}>
<p className={styles.titleText}>{title}</p>
<AntdInput name={name} value={value} disabled={disabled} onChange={onChange}/>
<AntdInput name={name} value={value} disabled={disabled} onChange={onChange} />
</div>
);
};

34
packages/core/src/mods/settingBar/components/json/index.tsx

@ -1,4 +1,4 @@
import React, {useState, useEffect} from 'react';
import React, { useState, useEffect } from 'react';
import 'antd/es/modal/style';
import 'antd/es/button/style';
@ -7,8 +7,8 @@ import styles from './index.module.less';
import AceEditor from 'react-ace';
import JsonView from 'react-json-view';
import {Button, Modal, message} from 'antd';
import {safeParse} from '../../../../utils';
import { Button, Modal, message } from 'antd';
import { safeParse } from '../../../../utils';
import 'ace-builds/src-noconflict/mode-json';
import 'ace-builds/src-noconflict/theme-dracula';
import 'ace-builds/src-noconflict/snippets/json';
@ -21,9 +21,8 @@ interface IJsonProps {
onValueChange: (value: string) => void;
}
const Json: React.FC<IJsonProps> = props => {
const {title, value, onValueChange} = props;
const Json: React.FC<IJsonProps> = (props) => {
const { title, value, onValueChange } = props;
const [visible, setVisible] = useState<boolean>(false);
// events
@ -49,11 +48,7 @@ const Json: React.FC<IJsonProps> = props => {
displayObjectSize={false}
src={safeParse(value)}
/>
<Button
block={true}
className={styles.btn}
onClick={onClickEdit}
>
<Button block={true} className={styles.btn} onClick={onClickEdit}>
</Button>
<EditModal
@ -77,22 +72,21 @@ interface IEditorModalProps {
const CODE_EDITOR_STYLE = {
width: '100%',
height: 400
height: 400,
};
const CODE_EDITOR_OPTIONS = {
useWorker: false
useWorker: false,
};
const EditModal: React.FC<IEditorModalProps> = props => {
const {visible, title, value, onOk, onCancel} = props;
const EditModal: React.FC<IEditorModalProps> = (props) => {
const { visible, title, value, onOk, onCancel } = props;
const [json, setJson] = useState<string>('');
// life
useEffect(() => {
// set value when opening modal
// clear content when closing modal
if(visible) {
if (visible) {
setJson(value);
} else {
setJson('');
@ -104,13 +98,13 @@ const EditModal: React.FC<IEditorModalProps> = props => {
try {
JSON.parse(json);
onOk(json);
} catch(err) {
} catch (error) {
message.error('内容必须是合法json');
console.log('save failed, the error is:', err.message);
console.log('save failed, the error is:', error.message);
}
};
const onChangeJson = (newJson: string): void => {
if(newJson !== json) {
if (newJson !== json) {
setJson(newJson);
}
};

31
packages/core/src/mods/settingBar/index.tsx

@ -1,24 +1,23 @@
import React, {useEffect, useReducer} from 'react';
import React, { useEffect, useReducer } from 'react';
import 'antd/es/tabs/style';
import 'antd/es/empty/style';
import styles from './index.module.less';
import {Empty, Tabs} from 'antd';
import { Empty, Tabs } from 'antd';
import Basic from './mods/basic';
import Config from './mods/config';
import {Graph} from '@antv/x6';
import { Graph } from '@antv/x6';
const {TabPane} = Tabs;
const { TabPane } = Tabs;
interface IProps {
flowChart: Graph;
}
const SettingBar: React.FC<IProps> = props => {
const {flowChart} = props;
const forceUpdate = useReducer(n => n + 1, 0)[1];
const SettingBar: React.FC<IProps> = (props) => {
const { flowChart } = props;
const forceUpdate = useReducer((n) => n + 1, 0)[1];
useEffect(() => {
flowChart.on('settingBar:forceUpdate', forceUpdate);
@ -27,16 +26,20 @@ const SettingBar: React.FC<IProps> = props => {
};
}, []);
const nodes = flowChart.getSelectedCells().filter(v => v.shape !== 'edge');
if(nodes.length === 1) {
const nodes = flowChart.getSelectedCells().filter((v) => v.shape !== 'edge');
if (nodes.length === 1) {
return (
<div className={styles.container}>
<Tabs tabBarGutter={0} defaultActiveKey={'basic'} tabBarStyle={{display: 'flex', flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Tabs
tabBarGutter={0}
defaultActiveKey={'basic'}
tabBarStyle={{ display: 'flex', flex: 1, justifyContent: 'center', alignItems: 'center' }}
>
<TabPane tab={'基础信息'} key={'basic'}>
<Basic selectedCell={nodes[0]}/>
<Basic selectedCell={nodes[0]} />
</TabPane>
<TabPane tab={'投放配置'} key={'config'}>
<Config selectedCell={nodes[0]}/>
<Config selectedCell={nodes[0]} />
</TabPane>
</Tabs>
</div>
@ -44,7 +47,7 @@ const SettingBar: React.FC<IProps> = props => {
} else {
return (
<div className={`${styles.container} ${styles.center}`}>
<Empty description={'请选择一个节点'} image={Empty.PRESENTED_IMAGE_SIMPLE}/>
<Empty description={'请选择一个节点'} image={Empty.PRESENTED_IMAGE_SIMPLE} />
</div>
);
}

33
packages/core/src/mods/settingBar/mods/basic/index.tsx

@ -1,8 +1,8 @@
import React, {useState, useEffect} from 'react';
import React, { useState, useEffect } from 'react';
import styles from './index.module.less';
import {Cell} from '@antv/x6';
import { Cell } from '@antv/x6';
import Json from '../../components/json';
import Code from '../../components/code';
import Input from '../../components/input';
@ -19,11 +19,10 @@ interface IBasicData {
configSchema: string;
}
const Basic: React.FC<IProps> = props => {
const {selectedCell} = props;
const Basic: React.FC<IProps> = (props) => {
const { selectedCell } = props;
const [data, setData] = useState<IBasicData>();
const {label, code, trigger, dependencies, configSchema} = data || {};
const { label, code, trigger, dependencies, configSchema } = data || {};
// life
useEffect(() => {
@ -32,16 +31,16 @@ const Basic: React.FC<IProps> = props => {
// events
const commonChange = (key: string, val: string): void => {
selectedCell.setData({[key]: val});
setData(Object.assign({}, data, {[key]: val}));
selectedCell.setData({ [key]: val });
setData(Object.assign({}, data, { [key]: val }));
};
const onChangeLabel = (val: string): void => {
commonChange('label', val);
selectedCell.setAttrs({label: {text: val}});
selectedCell.setAttrs({ label: { text: val } });
};
const onChangeConfigSchema = (val: string): void => {
commonChange('configSchema', val);
selectedCell.trigger('change:configSchema', {configSchema: val});
selectedCell.trigger('change:configSchema', { configSchema: val });
};
const onChangeCode = (val: string): void => commonChange('code', val);
const onChangeTrigger = (val: string): void => commonChange('trigger', val);
@ -49,12 +48,7 @@ const Basic: React.FC<IProps> = props => {
return (
<div className={styles.container}>
<Input
name={'label'}
title={'显示名称'}
value={label}
onValueChange={onChangeLabel}
/>
<Input name={'label'} title={'显示名称'} value={label} onValueChange={onChangeLabel} />
{selectedCell.shape === 'imove-start' && (
<Input
name={'trigger'}
@ -63,12 +57,7 @@ const Basic: React.FC<IProps> = props => {
onValueChange={onChangeTrigger}
/>
)}
<Code
name={'code'}
title={'代码'}
value={code}
onValueChange={onChangeCode}
/>
<Code name={'code'} title={'代码'} value={code} onValueChange={onChangeCode} />
<Json
name={'configSchema'}
title={'投放配置schema'}

41
packages/core/src/mods/settingBar/mods/config/index.tsx

@ -1,12 +1,12 @@
import React, {useState, useEffect} from 'react';
import React, { useState, useEffect } from 'react';
import styles from './index.module.less';
import {Cell} from '@antv/x6';
import {Button ,Empty} from 'antd';
import { Cell } from '@antv/x6';
import { Button, Empty } from 'antd';
import Input from '../../components/input';
import Checkbox from '../../components/checkbox';
import {safeParse} from '../../../../utils';
import { safeParse } from '../../../../utils';
// FIXME
// save original config in tempConfig when change to edit mode
@ -15,7 +15,7 @@ let tempConfig: any = null;
enum Mode {
edit = 'edit',
readOnly = 'readOnly'
readOnly = 'readOnly',
}
interface IConfig {
@ -26,19 +26,18 @@ interface IProps {
selectedCell: Cell;
}
const Config: React.FC<IProps> = props => {
const {selectedCell} = props;
const Config: React.FC<IProps> = (props) => {
const { selectedCell } = props;
const [mode, setMode] = useState<Mode>(Mode.readOnly);
const [configData, setConfigData] = useState<IConfig>({});
const [configSchema, setConfigSchema] = useState<IConfig>({});
// life
useEffect(() => {
const {configSchema, configData = {}} = selectedCell.getData() || {};
const { configSchema, configData = {} } = selectedCell.getData() || {};
setConfigData(configData);
setConfigSchema(safeParse(configSchema));
selectedCell.on('change:configSchema', (data: {configSchema: string}) => {
selectedCell.on('change:configSchema', (data: { configSchema: string }) => {
setConfigSchema(safeParse(data.configSchema));
});
return () => {
@ -48,7 +47,7 @@ const Config: React.FC<IProps> = props => {
// events
const onFieldValueChange = (key: string, value: any) => {
setConfigData(Object.assign({}, configData, {[key]: value}));
setConfigData(Object.assign({}, configData, { [key]: value }));
};
const onClickEdit = (): void => {
setMode(Mode.edit);
@ -60,11 +59,11 @@ const Config: React.FC<IProps> = props => {
};
const onClickSave = (): void => {
setMode(Mode.readOnly);
selectedCell.setData({configData});
selectedCell.setData({ configData });
};
// no config schema
if(!configSchema || Object.keys(configSchema).length === 0) {
if (!configSchema || Object.keys(configSchema).length === 0) {
return (
<div className={styles.container}>
<Empty
@ -78,7 +77,7 @@ const Config: React.FC<IProps> = props => {
return (
<div className={styles.container}>
{Object.keys(configSchema).map((key, idx) => {
const {title, description, type} = configSchema[key];
const { title, description, type } = configSchema[key];
const FieldComponent = Helper.getFieldComponent(type);
return (
<FieldComponent
@ -94,12 +93,16 @@ const Config: React.FC<IProps> = props => {
})}
{mode === Mode.readOnly ? (
<div className={styles.footContainer}>
<Button block onClick={onClickEdit}></Button>
<Button block onClick={onClickEdit}>
</Button>
</div>
) : (
<div className={styles.footContainer}>
<Button onClick={onClickCancel}></Button>
<Button className={styles.saveBtn} type={'primary'} onClick={onClickSave}></Button>
<Button className={styles.saveBtn} type={'primary'} onClick={onClickSave}>
</Button>
</div>
)}
</div>
@ -109,12 +112,12 @@ const Config: React.FC<IProps> = props => {
const Helper = {
getFieldComponent(type: string) {
if(type === 'boolean') {
if (type === 'boolean') {
return Checkbox;
} else {
return Input;
}
}
}
},
};
export default Config;

43
packages/core/src/mods/sideBar/index.tsx

@ -1,22 +1,22 @@
import React, {useState, useEffect, useRef} from 'react';
import React, { useState, useEffect, useRef } from 'react';
import 'antd/es/collapse/style';
import styles from './index.module.less';
import {Collapse} from 'antd';
import {Addon, Events, Graph, Node} from '@antv/x6';
import { Collapse } from 'antd';
import { Addon, Events, Graph, Node } from '@antv/x6';
import previewCellSchemaMap from '../../common/previewCell';
const {Dnd} = Addon;
const {Panel} = Collapse;
const { Dnd } = Addon;
const { Panel } = Collapse;
const CELL_SIZE = 80;
const CELL_SCALE = 0.7;
const GENERAL_GROUP = {
key: 'general',
name: '通用元件',
cells: ['imove-start-preview', 'imove-branch-preview', 'imove-behavior-preview']
cells: ['imove-start-preview', 'imove-branch-preview', 'imove-behavior-preview'],
};
const SHAPE_REFLECT_MAP: {[key: string]: string} = {
const SHAPE_REFLECT_MAP: { [key: string]: string } = {
'imove-start-preview': 'imove-start',
'imove-branch-preview': 'imove-branch',
'imove-behavior-preview': 'imove-behavior',
@ -32,9 +32,8 @@ interface ISideBarProps {
flowChart: Graph;
}
const SideBar: React.FC<ISideBarProps> = props => {
const {flowChart} = props;
const SideBar: React.FC<ISideBarProps> = (props) => {
const { flowChart } = props;
const [dnd, setDnd] = useState<Addon.Dnd>();
const [groups, setGroups] = useState<IGroupItem[]>([]);
@ -42,16 +41,16 @@ const SideBar: React.FC<ISideBarProps> = props => {
useEffect(() => {
// TODO: fetch to get custom group data
setGroups([GENERAL_GROUP]);
setDnd(new Dnd({target: flowChart, scaled: true}));
setDnd(new Dnd({ target: flowChart, scaled: true }));
}, []);
return (
<div className={styles.container}>
{dnd && (
<Collapse className={styles.collapse} defaultActiveKey={['general', 'custom']}>
{groups.map(group => (
{groups.map((group) => (
<Panel key={group.key} header={group.name}>
<PanelContent dnd={dnd} cells={group.cells}/>
<PanelContent dnd={dnd} cells={group.cells} />
</Panel>
))}
</Collapse>
@ -65,11 +64,11 @@ interface IPanelContentProps {
cells: string[];
}
const PanelContent: React.FC<IPanelContentProps> = props => {
const {dnd, cells} = props;
const PanelContent: React.FC<IPanelContentProps> = (props) => {
const { dnd, cells } = props;
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if(ref.current) {
if (ref.current) {
const graph = new Graph({
container: ref.current,
width: ref.current.offsetWidth,
@ -80,25 +79,25 @@ const PanelContent: React.FC<IPanelContentProps> = props => {
cells.forEach((cell: any, index: number) => {
const rowIdx = Math.floor(index / 3);
const colIdx = index % 3;
const {top} = previewCellSchemaMap[cell] || {};
const { top } = previewCellSchemaMap[cell] || {};
graph.addNode({
shape: cell,
x: colIdx * (CELL_SIZE + 16 / CELL_SCALE),
y: rowIdx * (CELL_SIZE + 16 / CELL_SCALE) + top
y: rowIdx * (CELL_SIZE + 16 / CELL_SCALE) + top,
});
});
graph.scale(CELL_SCALE, CELL_SCALE);
graph.on('cell:mousedown', (args: Events.EventArgs['cell:mousedown']) => {
const {node, e} = args;
const { node, e } = args;
let newNode = node;
if(SHAPE_REFLECT_MAP[node.shape]) {
newNode = Node.create({shape: SHAPE_REFLECT_MAP[node.shape]});
if (SHAPE_REFLECT_MAP[node.shape]) {
newNode = Node.create({ shape: SHAPE_REFLECT_MAP[node.shape] });
}
dnd.start(newNode, e);
});
}
}, []);
return <div className={styles.chart} ref={ref}/>;
return <div className={styles.chart} ref={ref} />;
};
export default SideBar;

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

@ -1,9 +1,9 @@
import React, {useEffect, useReducer} from 'react';
import React, { useEffect, useReducer } from 'react';
import 'antd/es/tooltip/style';
import styles from './index.module.less';
import {Graph} from '@antv/x6';
import { Graph } from '@antv/x6';
import widgets from './widgets';
import ModifyStatus from './widgets/modifyStatus';
@ -11,10 +11,9 @@ interface IProps {
flowChart: Graph;
}
const ToolBar: React.FC<IProps> = props => {
const {flowChart} = props;
const forceUpdate = useReducer(n => n + 1, 0)[1];
const ToolBar: React.FC<IProps> = (props) => {
const { flowChart } = props;
const forceUpdate = useReducer((n) => n + 1, 0)[1];
useEffect(() => {
flowChart.on('toolBar:forceUpdate', forceUpdate);
@ -28,11 +27,11 @@ const ToolBar: React.FC<IProps> = props => {
{widgets.map((group, index) => (
<div key={index} className={styles.group}>
{group.map((ToolItem, index) => {
return <ToolItem key={index} flowChart={flowChart}/>;
return <ToolItem key={index} flowChart={flowChart} />;
})}
</div>
))}
<ModifyStatus flowChart={flowChart}/>
<ModifyStatus flowChart={flowChart} />
</div>
);
};

18
packages/core/src/mods/toolBar/widgets/bgColor.tsx

@ -3,12 +3,12 @@ import React from 'react';
import 'antd/es/menu/style';
import styles from './index.module.less';
import {Graph} from '@antv/x6';
import {safeGet} from '../../../utils';
import { Graph } from '@antv/x6';
import { safeGet } from '../../../utils';
import XIcon from '../../../components/xIcon';
import ColorPicker from '../../../components/colorPicker';
import makeDropdownWidget from './common/makeDropdownWidget';
import {hasNodeSelected, getSelectedNodes} from '../../../utils/flowChartUtils';
import { hasNodeSelected, getSelectedNodes } from '../../../utils/flowChartUtils';
interface IProps {
flowChart: Graph;
@ -19,7 +19,7 @@ const options = {
getCurBgColor(flowChart: Graph) {
let bgColor = '#DDD';
const nodes = getSelectedNodes(flowChart);
if(!options.disabled(flowChart) && nodes.length > 0) {
if (!options.disabled(flowChart) && nodes.length > 0) {
bgColor = safeGet(nodes, '0.attrs.body.fill', '#575757');
}
return bgColor;
@ -28,20 +28,18 @@ const options = {
const bgColor = options.getCurBgColor(flowChart);
return (
<div className={styles.bgColorContainer}>
<XIcon className={styles.fillIcon} type={'icon-tianchong'}/>
<div className={styles.colorPreview} style={{backgroundColor: bgColor}}/>
<XIcon className={styles.fillIcon} type={'icon-tianchong'} />
<div className={styles.colorPreview} style={{ backgroundColor: bgColor }} />
</div>
);
},
getOverlay(flowChart: Graph, onChange: (data: any) => void) {
const bgColor = options.getCurBgColor(flowChart);
const onChangeComplete = (color: string) => onChange(color);
return (
<ColorPicker color={bgColor} onChangeComplete={onChangeComplete}/>
);
return <ColorPicker color={bgColor} onChangeComplete={onChangeComplete} />;
},
handler: (flowChart: Graph, value: any) => {
getSelectedNodes(flowChart).forEach(node => node.setAttrs({body: {fill: value}}));
getSelectedNodes(flowChart).forEach((node) => node.setAttrs({ body: { fill: value } }));
},
disabled(flowChart: Graph) {
return !hasNodeSelected(flowChart);

16
packages/core/src/mods/toolBar/widgets/bold.tsx

@ -1,32 +1,32 @@
import React from 'react';
import {Graph} from '@antv/x6';
import {safeGet} from '../../../utils';
import {BoldOutlined} from '@ant-design/icons';
import { Graph } from '@antv/x6';
import { safeGet } from '../../../utils';
import { BoldOutlined } from '@ant-design/icons';
import shortcuts from '../../../common/shortcuts';
import makeBtnWidget from './common/makeBtnWidget';
interface IProps {
flowChart: Graph
flowChart: Graph;
}
const Bold: React.FC<IProps> = makeBtnWidget({
tooltip: '加粗',
handler: shortcuts['bold'].handler,
handler: shortcuts.bold.handler,
getIcon() {
return <BoldOutlined/>;
return <BoldOutlined />;
},
disabled(flowChart: Graph) {
return flowChart.getSelectedCellCount() === 0;
},
selected(flowChart: Graph) {
const cells = flowChart.getSelectedCells();
if(cells.length > 0) {
if (cells.length > 0) {
return safeGet(cells, '0.attrs.label.fontWeight', 'normal') === 'bold';
} else {
return false;
}
}
},
});
export default Bold;

20
packages/core/src/mods/toolBar/widgets/borderColor.tsx

@ -3,12 +3,12 @@ import React from 'react';
import 'antd/es/menu/style';
import styles from './index.module.less';
import {Graph} from '@antv/x6';
import {safeGet} from '../../../utils';
import {HighlightOutlined} from '@ant-design/icons';
import { Graph } from '@antv/x6';
import { safeGet } from '../../../utils';
import { HighlightOutlined } from '@ant-design/icons';
import ColorPicker from '../../../components/colorPicker';
import makeDropdownWidget from './common/makeDropdownWidget';
import {hasNodeSelected, getSelectedNodes} from '../../../utils/flowChartUtils';
import { hasNodeSelected, getSelectedNodes } from '../../../utils/flowChartUtils';
interface IProps {
flowChart: Graph;
@ -19,7 +19,7 @@ const options = {
getCurBorderColor(flowChart: Graph) {
let borderColor = '#DDD';
const nodes = getSelectedNodes(flowChart);
if(!options.disabled(flowChart) && nodes.length > 0) {
if (!options.disabled(flowChart) && nodes.length > 0) {
borderColor = safeGet(nodes, '0.attrs.body.stroke', '#333');
}
return borderColor;
@ -28,20 +28,18 @@ const options = {
const borderColor = options.getCurBorderColor(flowChart);
return (
<div className={styles.borderColorContainer}>
<HighlightOutlined className={styles.borderColorIcon}/>
<div className={styles.colorPreview} style={{backgroundColor: borderColor}}/>
<HighlightOutlined className={styles.borderColorIcon} />
<div className={styles.colorPreview} style={{ backgroundColor: borderColor }} />
</div>
);
},
getOverlay(flowChart: Graph, onChange: (data: any) => void) {
const borderColor = options.getCurBorderColor(flowChart);
const onChangeComplete = (color: string) => onChange(color);
return (
<ColorPicker color={borderColor} onChangeComplete={onChangeComplete}/>
);
return <ColorPicker color={borderColor} onChangeComplete={onChangeComplete} />;
},
handler: (flowChart: Graph, value: any) => {
getSelectedNodes(flowChart).forEach(node => node.setAttrs({body: {stroke: value}}));
getSelectedNodes(flowChart).forEach((node) => node.setAttrs({ body: { stroke: value } }));
},
disabled(flowChart: Graph) {
return !hasNodeSelected(flowChart);

12
packages/core/src/mods/toolBar/widgets/bringToBack.tsx

@ -1,25 +1,25 @@
import React from 'react';
import {Graph} from '@antv/x6';
import { Graph } from '@antv/x6';
import XIcon from '../../../components/xIcon';
import makeBtnWidget from './common/makeBtnWidget';
import {getSelectedNodes, hasNodeSelected} from '../../../utils/flowChartUtils';
import { getSelectedNodes, hasNodeSelected } from '../../../utils/flowChartUtils';
interface IProps {
flowChart: Graph
flowChart: Graph;
}
const BringToBack: React.FC<IProps> = makeBtnWidget({
tooltip: '置于底层',
getIcon() {
return <XIcon type={'icon-bringtobottom'}/>;
return <XIcon type={'icon-bringtobottom'} />;
},
handler(flowChart: Graph) {
getSelectedNodes(flowChart).forEach(node => node.toBack());
getSelectedNodes(flowChart).forEach((node) => node.toBack());
},
disabled(flowChart: Graph) {
return !hasNodeSelected(flowChart);
}
},
});
export default BringToBack;

14
packages/core/src/mods/toolBar/widgets/bringToTop.tsx

@ -1,26 +1,26 @@
//at.alicdn.com/t/font_2024452_s39le1337k9.js
// at.alicdn.com/t/font_2024452_s39le1337k9.js
import React from 'react';
import {Graph} from '@antv/x6';
import { Graph } from '@antv/x6';
import XIcon from '../../../components/xIcon';
import makeBtnWidget from './common/makeBtnWidget';
import {getSelectedNodes, hasNodeSelected} from '../../../utils/flowChartUtils';
import { getSelectedNodes, hasNodeSelected } from '../../../utils/flowChartUtils';
interface IProps {
flowChart: Graph
flowChart: Graph;
}
const BringToTop: React.FC<IProps> = makeBtnWidget({
tooltip: '置于顶层',
getIcon() {
return <XIcon type={'icon-bringtotop'}/>;
return <XIcon type={'icon-bringtotop'} />;
},
handler(flowChart: Graph) {
getSelectedNodes(flowChart).forEach(node => node.toFront());
getSelectedNodes(flowChart).forEach((node) => node.toFront());
},
disabled(flowChart: Graph) {
return !hasNodeSelected(flowChart);
}
},
});
export default BringToTop;

20
packages/core/src/mods/toolBar/widgets/common/makeBtnWidget.tsx

@ -1,10 +1,10 @@
import React, {ReactElement} from 'react';
import React, { ReactElement } from 'react';
import 'antd/es/tooltip/style';
import styles from '../index.module.less';
import {Tooltip} from 'antd';
import {Graph} from '@antv/x6';
import { Tooltip } from 'antd';
import { Graph } from '@antv/x6';
interface IOptions {
tooltip: string;
@ -20,23 +20,23 @@ interface IBtnWidgetProps {
const makeBtnWidget = (options: IOptions) => {
const Widget: React.FC<IBtnWidgetProps> = (props) => {
const {flowChart} = props;
const {tooltip, getIcon, handler} = options;
const { flowChart } = props;
const { tooltip, getIcon, handler } = options;
const iconWrapperCls = [styles.btnWidget];
let {disabled = false, selected = false} = options;
if(typeof disabled === 'function') {
let { disabled = false, selected = false } = options;
if (typeof disabled === 'function') {
disabled = disabled(flowChart);
disabled && iconWrapperCls.push(styles.disabled);
}
if(typeof selected === 'function') {
if (typeof selected === 'function') {
selected = selected(flowChart);
selected && iconWrapperCls.push(styles.selected);
}
const onClick = (): void => {
if(disabled) return;
if (disabled) return;
handler(flowChart);
flowChart.trigger('toolBar:forceUpdate');
}
};
return (
<Tooltip title={tooltip}>
<div className={iconWrapperCls.join(' ')} onClick={onClick}>

20
packages/core/src/mods/toolBar/widgets/common/makeDropdownWidget.tsx

@ -1,12 +1,12 @@
import React, {ReactElement} from 'react';
import React, { ReactElement } from 'react';
import 'antd/es/tooltip/style';
import 'antd/es/dropdown/style';
import styles from '../index.module.less';
import {Graph} from '@antv/x6';
import {Tooltip, Dropdown} from 'antd';
import {CaretDownOutlined} from '@ant-design/icons';
import { Graph } from '@antv/x6';
import { Tooltip, Dropdown } from 'antd';
import { CaretDownOutlined } from '@ant-design/icons';
interface IOptions {
tooltip: string;
@ -22,16 +22,16 @@ interface IDropdownWidgetProps {
const makeDropdownWidget = (options: IOptions) => {
const Widget: React.FC<IDropdownWidgetProps> = (props) => {
const {flowChart} = props;
const {tooltip, getIcon, getOverlay, handler} = options;
const { flowChart } = props;
const { tooltip, getIcon, getOverlay, handler } = options;
const iconWrapperCls = [styles.btnWidget];
let {disabled = false} = options;
if(typeof disabled === 'function') {
let { disabled = false } = options;
if (typeof disabled === 'function') {
disabled = disabled(flowChart);
disabled && iconWrapperCls.push(styles.disabled);
}
const onChange = (data: any): void => {
if(disabled) return;
if (disabled) return;
handler(flowChart, data);
flowChart.trigger('toolBar:forceUpdate');
};
@ -39,7 +39,7 @@ const makeDropdownWidget = (options: IOptions) => {
<Tooltip title={tooltip}>
<Dropdown disabled={disabled} overlay={getOverlay(flowChart, onChange)} trigger={['click']}>
<div className={iconWrapperCls.join(' ')}>
{getIcon(flowChart)} <CaretDownOutlined className={styles.caret}/>
{getIcon(flowChart)} <CaretDownOutlined className={styles.caret} />
</div>
</Dropdown>
</Tooltip>

12
packages/core/src/mods/toolBar/widgets/fitWindow.tsx

@ -1,21 +1,21 @@
import React from 'react';
import {Graph} from '@antv/x6';
import { Graph } from '@antv/x6';
import makeBtnWidget from './common/makeBtnWidget';
import {FullscreenExitOutlined} from '@ant-design/icons';
import { FullscreenExitOutlined } from '@ant-design/icons';
interface IProps {
flowChart: Graph
flowChart: Graph;
}
const FitWindow: React.FC<IProps> = makeBtnWidget({
tooltip: '适配窗口',
getIcon() {
return <FullscreenExitOutlined/>;
return <FullscreenExitOutlined />;
},
handler(flowChart: Graph) {
flowChart.zoomToFit({minScale: 0.5, maxScale: 1});
}
flowChart.zoomToFit({ minScale: 0.5, maxScale: 1 });
},
});
export default FitWindow;

20
packages/core/src/mods/toolBar/widgets/fontSize.tsx

@ -2,34 +2,32 @@ import React from 'react';
import 'antd/es/menu/style';
import {Menu} from 'antd';
import {Graph} from '@antv/x6';
import {safeGet} from '../../../utils';
import { Menu } from 'antd';
import { Graph } from '@antv/x6';
import { safeGet } from '../../../utils';
import makeDropdownWidget from './common/makeDropdownWidget';
interface IProps {
flowChart: Graph
flowChart: Graph;
}
const MenuItem = Menu.Item;
const FONT_SIZE_SET = [
9, 10, 11, 12, 13, 14, 15, 16, 19, 22, 24, 29, 32
];
const FONT_SIZE_SET = [9, 10, 11, 12, 13, 14, 15, 16, 19, 22, 24, 29, 32];
const FontSize: React.FC<IProps> = makeDropdownWidget({
tooltip: '字号',
getIcon(flowChart: Graph) {
let fontSize = 14;
const cells = flowChart.getSelectedCells();
if(cells.length > 0) {
if (cells.length > 0) {
fontSize = safeGet(cells, '0.attrs.label.fontSize', 14);
}
return <span style={{fontSize: 14}}>{fontSize}px</span>;
return <span style={{ fontSize: 14 }}>{fontSize}px</span>;
},
getOverlay(flowChart: Graph, onChange: (data: any) => void) {
return (
<Menu onClick={(args) => onChange(args.key)}>
{FONT_SIZE_SET.map(fontSize => (
{FONT_SIZE_SET.map((fontSize) => (
<MenuItem key={fontSize}>
<span>{fontSize} px</span>
</MenuItem>
@ -38,7 +36,7 @@ const FontSize: React.FC<IProps> = makeDropdownWidget({
);
},
handler: (flowChart: Graph, value: any) => {
flowChart.getSelectedCells().forEach(cell => cell.setAttrs({label: {fontSize: value}}));
flowChart.getSelectedCells().forEach((cell) => cell.setAttrs({ label: { fontSize: value } }));
},
disabled(flowChart: Graph) {
return flowChart.getSelectedCellCount() === 0;

48
packages/core/src/mods/toolBar/widgets/horizontalAlign.tsx

@ -2,12 +2,12 @@ import React, { ReactElement } from 'react';
import 'antd/es/menu/style';
import {Menu} from 'antd';
import {Graph} from '@antv/x6';
import {safeGet} from '../../../utils';
import { Menu } from 'antd';
import { Graph } from '@antv/x6';
import { safeGet } from '../../../utils';
import makeDropdownWidget from './common/makeDropdownWidget';
import {hasNodeSelected, getSelectedNodes} from '../../../utils/flowChartUtils';
import {AlignLeftOutlined, AlignCenterOutlined, AlignRightOutlined} from '@ant-design/icons';
import { hasNodeSelected, getSelectedNodes } from '../../../utils/flowChartUtils';
import { AlignLeftOutlined, AlignCenterOutlined, AlignRightOutlined } from '@ant-design/icons';
interface IProps {
flowChart: Graph;
@ -22,48 +22,48 @@ interface AlignItem {
textAnchor: string;
align: {
horizontal: string;
}
}
};
};
}
const MenuItem = Menu.Item;
const ALIGN_MAP: {[key: string]: AlignItem} = {
const ALIGN_MAP: { [key: string]: AlignItem } = {
left: {
text: '左对齐',
icon: <AlignLeftOutlined/>,
icon: <AlignLeftOutlined />,
attrs: {
refX: 0,
refX2: 5,
textAnchor: 'start',
align: {
horizontal: 'left'
}
}
horizontal: 'left',
},
},
},
center: {
text: '居中对齐',
icon: <AlignCenterOutlined/>,
icon: <AlignCenterOutlined />,
attrs: {
refX: 0.5,
refX2: 0,
textAnchor: 'middle',
align: {
horizontal: 'center'
}
}
horizontal: 'center',
},
},
},
right: {
text: '右对齐',
icon: <AlignRightOutlined/>,
icon: <AlignRightOutlined />,
attrs: {
refX: 0.99,
refX2: -5,
textAnchor: 'end',
align: {
horizontal: 'right'
}
}
}
horizontal: 'right',
},
},
},
};
const HorizontalAlign: React.FC<IProps> = makeDropdownWidget({
@ -71,14 +71,14 @@ const HorizontalAlign: React.FC<IProps> = makeDropdownWidget({
getIcon(flowChart: Graph) {
let alignType = 'center';
const nodes = getSelectedNodes(flowChart);
if(nodes.length > 0) {
if (nodes.length > 0) {
alignType = safeGet(nodes, '0.attrs.label.align.horizontal', 'center');
}
return ALIGN_MAP[alignType].icon;
},
getOverlay(flowChart: Graph, onChange: (data: any) => void) {
return (
<Menu onClick={({key}) => onChange(ALIGN_MAP[key].attrs)}>
<Menu onClick={({ key }) => onChange(ALIGN_MAP[key].attrs)}>
{Object.keys(ALIGN_MAP).map((alignType) => (
<MenuItem key={alignType}>
{ALIGN_MAP[alignType].icon}
@ -89,7 +89,7 @@ const HorizontalAlign: React.FC<IProps> = makeDropdownWidget({
);
},
handler: (flowChart: Graph, value: any) => {
getSelectedNodes(flowChart).forEach(node => node.setAttrs({label: value}));
getSelectedNodes(flowChart).forEach((node) => node.setAttrs({ label: value }));
},
disabled(flowChart: Graph) {
return !hasNodeSelected(flowChart);

4
packages/core/src/mods/toolBar/widgets/index.tsx

@ -1,6 +1,6 @@
import React from 'react';
import {Graph} from '@antv/x6';
import { Graph } from '@antv/x6';
import Save from './save';
import Undo from './undo';
@ -30,7 +30,7 @@ const tools: React.FC<IProps>[][] = [
[FontSize, Bold, Italic, Underline],
[TextColor, BgColor, BorderColor, LineStyle],
[HorizontalAlign, VerticalAlign],
[BringToTop, BringToBack]
[BringToTop, BringToBack],
];
export default tools;

16
packages/core/src/mods/toolBar/widgets/italic.tsx

@ -1,32 +1,32 @@
import React from 'react';
import {Graph} from '@antv/x6';
import {safeGet} from '../../../utils';
import { Graph } from '@antv/x6';
import { safeGet } from '../../../utils';
import makeBtnWidget from './common/makeBtnWidget';
import {ItalicOutlined} from '@ant-design/icons';
import { ItalicOutlined } from '@ant-design/icons';
import shortcuts from '../../../common/shortcuts';
interface IProps {
flowChart: Graph
flowChart: Graph;
}
const Italic: React.FC<IProps> = makeBtnWidget({
tooltip: '斜体',
handler: shortcuts['italic'].handler,
handler: shortcuts.italic.handler,
getIcon() {
return <ItalicOutlined/>;
return <ItalicOutlined />;
},
disabled(flowChart: Graph) {
return flowChart.getSelectedCellCount() === 0;
},
selected(flowChart: Graph) {
const cells = flowChart.getSelectedCells();
if(cells.length > 0) {
if (cells.length > 0) {
return safeGet(cells, '0.attrs.label.fontStyle', 'normal') === 'italic';
} else {
return false;
}
}
},
});
export default Italic;

34
packages/core/src/mods/toolBar/widgets/lineStyle.tsx

@ -1,13 +1,13 @@
import React, {ReactElement} from 'react';
import React, { ReactElement } from 'react';
import 'antd/es/menu/style';
import {Menu} from 'antd';
import {Graph} from '@antv/x6';
import {safeGet} from '../../../utils';
import { Menu } from 'antd';
import { Graph } from '@antv/x6';
import { safeGet } from '../../../utils';
import XIcon from '../../../components/xIcon';
import makeDropdownWidget from './common/makeDropdownWidget';
import {hasEdgeSelected, getSelectedEdges} from '../../../utils/flowChartUtils';
import { hasEdgeSelected, getSelectedEdges } from '../../../utils/flowChartUtils';
interface IProps {
flowChart: Graph;
@ -19,27 +19,27 @@ interface LineStyleItem {
attrs: {
type: string;
strokeDasharray: string;
}
};
}
const MenuItem = Menu.Item;
const LINE_STYLE_MAP: {[key: string]: LineStyleItem} = {
const LINE_STYLE_MAP: { [key: string]: LineStyleItem } = {
straight: {
text: '直线',
icon: <XIcon type={'icon-line'}/>,
icon: <XIcon type={'icon-line'} />,
attrs: {
type: 'straight',
strokeDasharray: '5, 0'
}
strokeDasharray: '5, 0',
},
},
dashed: {
text: '虚线',
icon: <XIcon type={'icon-dash'}/>,
icon: <XIcon type={'icon-dash'} />,
attrs: {
type: 'dashed',
strokeDasharray: '5, 5'
}
}
strokeDasharray: '5, 5',
},
},
};
const LineStyle: React.FC<IProps> = makeDropdownWidget({
@ -47,14 +47,14 @@ const LineStyle: React.FC<IProps> = makeDropdownWidget({
getIcon(flowChart: Graph) {
let lineType = 'straight';
const edges = getSelectedEdges(flowChart);
if(edges.length > 0) {
if (edges.length > 0) {
lineType = safeGet(edges, '0.attrs.line.type', 'straight');
}
return LINE_STYLE_MAP[lineType].icon;
},
getOverlay(flowChart: Graph, onChange: (data: any) => void) {
return (
<Menu onClick={({key}) => onChange(LINE_STYLE_MAP[key].attrs)}>
<Menu onClick={({ key }) => onChange(LINE_STYLE_MAP[key].attrs)}>
{Object.keys(LINE_STYLE_MAP).map((lineType: string) => (
<MenuItem key={lineType}>
{LINE_STYLE_MAP[lineType].icon}
@ -65,7 +65,7 @@ const LineStyle: React.FC<IProps> = makeDropdownWidget({
);
},
handler: (flowChart: Graph, value: any) => {
getSelectedEdges(flowChart).forEach(edge => edge.setAttrs({line: value}));
getSelectedEdges(flowChart).forEach((edge) => edge.setAttrs({ line: value }));
},
disabled(flowChart: Graph) {
return !hasEdgeSelected(flowChart);

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

@ -8,35 +8,34 @@ enum Status {
pending = 0,
syncing = 1,
successful = 2,
failed = 3
failed = 3,
}
interface IProps {
flowChart: Graph
flowChart: Graph;
}
const statusMap = {
[Status.pending]: {
color: '',
text: ''
text: '',
},
[Status.syncing]: {
color: '#999',
text: '正在保存...'
text: '正在保存...',
},
[Status.successful]: {
color: '#999',
text: '所有更改已保存'
text: '所有更改已保存',
},
[Status.failed]: {
color: '#EC5B56',
text: '同步失败,进入离线模式'
}
text: '同步失败,进入离线模式',
},
};
const ModifyStatus: React.FC<IProps> = (props) => {
const {flowChart} = props;
const { flowChart } = props;
const [status, setStatus] = useState<Status>(Status.pending);
useEffect(() => {
@ -44,8 +43,8 @@ const ModifyStatus: React.FC<IProps> = (props) => {
setStatus(Status.syncing);
});
flowChart.on('graph:modified', (res: any) => {
const {success} = res;
if(success) {
const { success } = res;
if (success) {
setStatus(Status.successful);
} else {
setStatus(Status.failed);
@ -57,10 +56,10 @@ const ModifyStatus: React.FC<IProps> = (props) => {
};
}, []);
const {color, text} = statusMap[status];
const { color, text } = statusMap[status];
return status === Status.pending ? null : (
<div className={styles.modifyStatusContainer}>
<span style={{color}}>{text}</span>
<span style={{ color }}>{text}</span>
</div>
);
};

12
packages/core/src/mods/toolBar/widgets/redo.tsx

@ -1,23 +1,23 @@
import React from 'react';
import {Graph} from '@antv/x6';
import {RedoOutlined} from '@ant-design/icons';
import { Graph } from '@antv/x6';
import { RedoOutlined } from '@ant-design/icons';
import shortcuts from '../../../common/shortcuts';
import makeBtnWidget from './common/makeBtnWidget';
interface IProps {
flowChart: Graph
flowChart: Graph;
}
const Save: React.FC<IProps> = makeBtnWidget({
tooltip: '重做',
handler: shortcuts['redo'].handler,
handler: shortcuts.redo.handler,
getIcon() {
return <RedoOutlined/>;
return <RedoOutlined />;
},
disabled(flowChart: Graph) {
return !flowChart.canRedo();
}
},
});
export default Save;

12
packages/core/src/mods/toolBar/widgets/save.tsx

@ -1,20 +1,20 @@
import React from 'react';
import {Graph} from '@antv/x6';
import {SaveOutlined} from '@ant-design/icons';
import { Graph } from '@antv/x6';
import { SaveOutlined } from '@ant-design/icons';
import shortcuts from '../../../common/shortcuts';
import makeBtnWidget from './common/makeBtnWidget';
interface IProps {
flowChart: Graph
flowChart: Graph;
}
const Save: React.FC<IProps> = makeBtnWidget({
tooltip: '保存',
handler: shortcuts['save'].handler,
handler: shortcuts.save.handler,
getIcon() {
return <SaveOutlined/>;
}
return <SaveOutlined />;
},
});
export default Save;

18
packages/core/src/mods/toolBar/widgets/textColor.tsx

@ -3,12 +3,12 @@ import React from 'react';
import 'antd/es/menu/style';
import styles from './index.module.less';
import {Graph} from '@antv/x6';
import {safeGet} from '../../../utils';
import { Graph } from '@antv/x6';
import { safeGet } from '../../../utils';
import XIcon from '../../../components/xIcon';
import ColorPicker from '../../../components/colorPicker';
import makeDropdownWidget from './common/makeDropdownWidget';
import {hasNodeSelected, getSelectedNodes} from '../../../utils/flowChartUtils';
import { hasNodeSelected, getSelectedNodes } from '../../../utils/flowChartUtils';
interface IProps {
flowChart: Graph;
@ -19,7 +19,7 @@ const options = {
getCurTextColor(flowChart: Graph) {
let textColor = '#DDD';
const nodes = getSelectedNodes(flowChart);
if(nodes.length > 0) {
if (nodes.length > 0) {
textColor = safeGet(nodes, '0.attrs.label.fill', '#575757');
}
return textColor;
@ -28,20 +28,18 @@ const options = {
const textColor = options.getCurTextColor(flowChart);
return (
<div className={styles.textColorContainer}>
<XIcon className={styles.textIcon} type={'icon-A'}/>
<div className={styles.colorPreview} style={{backgroundColor: textColor}}/>
<XIcon className={styles.textIcon} type={'icon-A'} />
<div className={styles.colorPreview} style={{ backgroundColor: textColor }} />
</div>
);
},
getOverlay(flowChart: Graph, onChange: (data: any) => void) {
const textColor = options.getCurTextColor(flowChart);
const onChangeComplete = (color: string) => onChange(color);
return (
<ColorPicker color={textColor} onChangeComplete={onChangeComplete}/>
);
return <ColorPicker color={textColor} onChangeComplete={onChangeComplete} />;
},
handler: (flowChart: Graph, value: any) => {
flowChart.getSelectedCells().forEach(node => node.setAttrs({label: {fill: value}}));
flowChart.getSelectedCells().forEach((node) => node.setAttrs({ label: { fill: value } }));
},
disabled(flowChart: Graph) {
return !hasNodeSelected(flowChart);

16
packages/core/src/mods/toolBar/widgets/underline.tsx

@ -1,32 +1,32 @@
import React from 'react';
import {Graph} from '@antv/x6';
import {safeGet} from '../../../utils';
import { Graph } from '@antv/x6';
import { safeGet } from '../../../utils';
import shortcuts from '../../../common/shortcuts';
import makeBtnWidget from './common/makeBtnWidget';
import {UnderlineOutlined} from '@ant-design/icons';
import { UnderlineOutlined } from '@ant-design/icons';
interface IProps {
flowChart: Graph
flowChart: Graph;
}
const Underline: React.FC<IProps> = makeBtnWidget({
tooltip: '下划线',
handler: shortcuts['underline'].handler,
handler: shortcuts.underline.handler,
getIcon() {
return <UnderlineOutlined/>;
return <UnderlineOutlined />;
},
disabled(flowChart: Graph) {
return flowChart.getSelectedCellCount() === 0;
},
selected(flowChart: Graph) {
const cells = flowChart.getSelectedCells();
if(cells.length > 0) {
if (cells.length > 0) {
return safeGet(cells, '0.attrs.label.textDecoration', 'none') === 'underline';
} else {
return false;
}
}
},
});
export default Underline;

12
packages/core/src/mods/toolBar/widgets/undo.tsx

@ -1,23 +1,23 @@
import React from 'react';
import {Graph} from '@antv/x6';
import {UndoOutlined} from '@ant-design/icons';
import { Graph } from '@antv/x6';
import { UndoOutlined } from '@ant-design/icons';
import shortcuts from '../../../common/shortcuts';
import makeBtnWidget from './common/makeBtnWidget';
interface IProps {
flowChart: Graph
flowChart: Graph;
}
const Save: React.FC<IProps> = makeBtnWidget({
tooltip: '撤销',
handler: shortcuts['undo'].handler,
handler: shortcuts.undo.handler,
getIcon() {
return <UndoOutlined/>;
return <UndoOutlined />;
},
disabled(flowChart: Graph) {
return !flowChart.canUndo();
}
},
});
export default Save;

54
packages/core/src/mods/toolBar/widgets/verticalAlign.tsx

@ -1,13 +1,17 @@
import React, {ReactElement} from 'react';
import React, { ReactElement } from 'react';
import 'antd/es/menu/style';
import {Menu} from 'antd';
import {Graph} from '@antv/x6';
import {safeGet} from '../../../utils';
import { Menu } from 'antd';
import { Graph } from '@antv/x6';
import { safeGet } from '../../../utils';
import makeDropdownWidget from './common/makeDropdownWidget';
import {hasNodeSelected, getSelectedNodes} from '../../../utils/flowChartUtils';
import {VerticalAlignTopOutlined, VerticalAlignMiddleOutlined, VerticalAlignBottomOutlined} from '@ant-design/icons';
import { hasNodeSelected, getSelectedNodes } from '../../../utils/flowChartUtils';
import {
VerticalAlignTopOutlined,
VerticalAlignMiddleOutlined,
VerticalAlignBottomOutlined,
} from '@ant-design/icons';
interface IProps {
flowChart: Graph;
@ -22,48 +26,48 @@ interface AlignItem {
textVerticalAlign: string;
align: {
vertical: string;
}
}
};
};
}
const MenuItem = Menu.Item;
const ALIGN_MAP: {[key: string]: AlignItem} = {
const ALIGN_MAP: { [key: string]: AlignItem } = {
top: {
text: '上对齐',
icon: <VerticalAlignTopOutlined/>,
icon: <VerticalAlignTopOutlined />,
attrs: {
refY: 0,
refY2: 10,
textVerticalAlign: 'start',
align: {
vertical: 'top'
}
}
vertical: 'top',
},
},
},
center: {
text: '居中对齐',
icon: <VerticalAlignMiddleOutlined/>,
icon: <VerticalAlignMiddleOutlined />,
attrs: {
refY: 0.5,
refY2: 0,
textVerticalAlign: 'middle',
align: {
vertical: 'center'
}
}
vertical: 'center',
},
},
},
bottom: {
text: '下对齐',
icon: <VerticalAlignBottomOutlined/>,
icon: <VerticalAlignBottomOutlined />,
attrs: {
refY: 0.99,
refY2: -10,
textVerticalAlign: 'end',
align: {
vertical: 'bottom'
}
}
}
vertical: 'bottom',
},
},
},
};
const VerticalAlign: React.FC<IProps> = makeDropdownWidget({
@ -71,14 +75,14 @@ const VerticalAlign: React.FC<IProps> = makeDropdownWidget({
getIcon(flowChart: Graph) {
let alignType = 'center';
const nodes = getSelectedNodes(flowChart);
if(nodes.length > 0) {
if (nodes.length > 0) {
alignType = safeGet(nodes, '0.attrs.label.align.vertical', 'center');
}
return ALIGN_MAP[alignType].icon;
},
getOverlay(flowChart: Graph, onChange: (data: any) => void) {
return (
<Menu onClick={({key}) => onChange(ALIGN_MAP[key].attrs)}>
<Menu onClick={({ key }) => onChange(ALIGN_MAP[key].attrs)}>
{Object.keys(ALIGN_MAP).map((alignType: string) => (
<MenuItem key={alignType}>
{ALIGN_MAP[alignType].icon}
@ -89,7 +93,7 @@ const VerticalAlign: React.FC<IProps> = makeDropdownWidget({
);
},
handler: (flowChart: Graph, value: any) => {
getSelectedNodes(flowChart).forEach(node => node.setAttrs({label: value}));
getSelectedNodes(flowChart).forEach((node) => node.setAttrs({ label: value }));
},
disabled(flowChart: Graph) {
return !hasNodeSelected(flowChart);

34
packages/core/src/mods/toolBar/widgets/zoom.tsx

@ -1,14 +1,14 @@
import React, {useState, useEffect} from 'react';
import React, { useState, useEffect } from 'react';
import styles from './index.module.less';
import {Graph} from '@antv/x6';
import { Graph } from '@antv/x6';
import shortcuts from '../../../common/shortcuts';
import makeBtnWidget from './common/makeBtnWidget';
import {ZoomOutOutlined, ZoomInOutlined} from '@ant-design/icons';
import { ZoomOutOutlined, ZoomInOutlined } from '@ant-design/icons';
interface IProps {
flowChart: Graph
flowChart: Graph;
}
const MIN_ZOOM = 0.5;
@ -16,28 +16,28 @@ const MAX_ZOOM = 1.5;
const ZoomOut: React.FC<IProps> = makeBtnWidget({
tooltip: '缩小',
handler: shortcuts['zoomOut'].handler,
handler: shortcuts.zoomOut.handler,
getIcon() {
return <ZoomOutOutlined/>;
return <ZoomOutOutlined />;
},
disabled(flowChart: Graph) {
return flowChart.zoom() <= MIN_ZOOM;
}
},
});
const ZoomIn: React.FC<IProps> = makeBtnWidget({
tooltip: '放大',
handler: shortcuts['zoomIn'].handler,
handler: shortcuts.zoomIn.handler,
getIcon() {
return <ZoomInOutlined/>;
return <ZoomInOutlined />;
},
disabled(flowChart: Graph) {
return flowChart.zoom() >= MAX_ZOOM;
}
},
});
const Zoom: React.FC<IProps> = props => {
const {flowChart} = props;
const Zoom: React.FC<IProps> = (props) => {
const { flowChart } = props;
const [scale, setScale] = useState<number>(flowChart.zoom());
useEffect(() => {
flowChart.on('scale', () => {
@ -46,11 +46,9 @@ const Zoom: React.FC<IProps> = props => {
}, [flowChart]);
return (
<div className={styles.zoomContainer}>
<ZoomOut {...props}/>
<span className={styles.zoomText}>
{Helper.scaleFormatter(scale)}
</span>
<ZoomIn {...props}/>
<ZoomOut {...props} />
<span className={styles.zoomText}>{Helper.scaleFormatter(scale)}</span>
<ZoomIn {...props} />
</div>
);
};
@ -58,7 +56,7 @@ const Zoom: React.FC<IProps> = props => {
const Helper = {
scaleFormatter(scale: number): string {
return (scale * 100).toFixed(0) + '%';
}
},
};
export default Zoom;

4
packages/core/src/typings.d.ts

@ -1,9 +1,9 @@
declare module '*.module.css' {
const classes: {[key: string]: string};
const classes: { [key: string]: string };
export default classes;
}
declare module '*.less' {
const classes: {[key: string]: string};
const classes: { [key: string]: string };
export default classes;
}

8
packages/core/src/utils/flowChartUtils.ts

@ -1,8 +1,8 @@
import {Cell, Graph} from "@antv/x6";
import { Cell, Graph } from '@antv/x6';
export const hasCellSelected = (flowChart: Graph): boolean => {
return flowChart.getSelectedCellCount() > 0;
}
};
export const hasNodeSelected = (flowChart: Graph): boolean => {
return flowChart.getSelectedCells().filter((cell: Cell) => cell.isNode()).length > 0;
@ -14,8 +14,8 @@ export const hasEdgeSelected = (flowChart: Graph): boolean => {
export const getSelectedNodes = (flowChart: Graph): Cell[] => {
return flowChart.getSelectedCells().filter((cell: Cell) => cell.isNode());
}
};
export const getSelectedEdges = (flowChart: Graph): Cell[] => {
return flowChart.getSelectedCells().filter((cell: Cell) => cell.isEdge());
}
};

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

@ -1,23 +1,22 @@
import { parse } from 'query-string';
export const safeParse = (json: string): Object => {
export const safeParse = (json: string): Record<string, any> => {
try {
return JSON.parse(json);
} catch(err) {
} catch (error) {
return {};
}
};
export const safeGet = (obj: any, keyChain: string, defaultVal?: any): any => {
if(typeof obj !== 'object' || obj === null) {
if (typeof obj !== 'object' || obj === null) {
return defaultVal;
}
let val = obj;
const keys = keyChain.split('.');
for(const key of keys) {
if(val[key] === undefined) {
for (const key of keys) {
if (val[key] === undefined) {
return defaultVal;
} else {
val = val[key];
@ -34,6 +33,6 @@ const parseConfig = {
parseBooleans: false,
};
export const parseQuery = (): {[key: string]: any} => {
export const parseQuery = (): { [key: string]: any } => {
return parse(location.search, parseConfig);
};

4
packages/plugin-store/src/index.ts

@ -15,9 +15,9 @@ const plugin = (logic: any) => {
},
remove(key: string) {
delete logic[storeSymbol][key];
}
},
};
}
},
};
};

Loading…
Cancel
Save