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. 17
      package-lock.json
  7. 12
      package.json
  8. 3
      packages/cli/package.json
  9. 3
      packages/cli/src/cmd/base/index.js
  10. 14
      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. 32
      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. 2
      packages/core/src/common/const.ts
  28. 10
      packages/core/src/common/previewCell/behavior.ts
  29. 10
      packages/core/src/common/previewCell/branch.ts
  30. 4
      packages/core/src/common/previewCell/index.ts
  31. 8
      packages/core/src/common/previewCell/start.ts
  32. 75
      packages/core/src/common/shortcuts.ts
  33. 15
      packages/core/src/components/colorPicker/index.tsx
  34. 5
      packages/core/src/components/miniMapSimpleNode/index.tsx
  35. 4
      packages/core/src/components/xIcon/index.tsx
  36. 12
      packages/core/src/index.tsx
  37. 59
      packages/core/src/mods/flowChart/createFlowChart.ts
  38. 23
      packages/core/src/mods/flowChart/index.tsx
  39. 38
      packages/core/src/mods/flowChart/registerServerStorage.ts
  40. 46
      packages/core/src/mods/header/connectStatus/editModal.tsx
  41. 69
      packages/core/src/mods/header/connectStatus/index.tsx
  42. 10
      packages/core/src/mods/header/index.tsx
  43. 31
      packages/core/src/mods/layout/index.tsx
  44. 14
      packages/core/src/mods/settingBar/components/checkbox/index.tsx
  45. 28
      packages/core/src/mods/settingBar/components/code/index.tsx
  46. 10
      packages/core/src/mods/settingBar/components/input/index.tsx
  47. 34
      packages/core/src/mods/settingBar/components/json/index.tsx
  48. 31
      packages/core/src/mods/settingBar/index.tsx
  49. 33
      packages/core/src/mods/settingBar/mods/basic/index.tsx
  50. 41
      packages/core/src/mods/settingBar/mods/config/index.tsx
  51. 43
      packages/core/src/mods/sideBar/index.tsx
  52. 15
      packages/core/src/mods/toolBar/index.tsx
  53. 18
      packages/core/src/mods/toolBar/widgets/bgColor.tsx
  54. 16
      packages/core/src/mods/toolBar/widgets/bold.tsx
  55. 22
      packages/core/src/mods/toolBar/widgets/borderColor.tsx
  56. 14
      packages/core/src/mods/toolBar/widgets/bringToBack.tsx
  57. 16
      packages/core/src/mods/toolBar/widgets/bringToTop.tsx
  58. 24
      packages/core/src/mods/toolBar/widgets/common/makeBtnWidget.tsx
  59. 20
      packages/core/src/mods/toolBar/widgets/common/makeDropdownWidget.tsx
  60. 12
      packages/core/src/mods/toolBar/widgets/fitWindow.tsx
  61. 20
      packages/core/src/mods/toolBar/widgets/fontSize.tsx
  62. 48
      packages/core/src/mods/toolBar/widgets/horizontalAlign.tsx
  63. 6
      packages/core/src/mods/toolBar/widgets/index.tsx
  64. 16
      packages/core/src/mods/toolBar/widgets/italic.tsx
  65. 34
      packages/core/src/mods/toolBar/widgets/lineStyle.tsx
  66. 25
      packages/core/src/mods/toolBar/widgets/modifyStatus.tsx
  67. 12
      packages/core/src/mods/toolBar/widgets/redo.tsx
  68. 12
      packages/core/src/mods/toolBar/widgets/save.tsx
  69. 18
      packages/core/src/mods/toolBar/widgets/textColor.tsx
  70. 16
      packages/core/src/mods/toolBar/widgets/underline.tsx
  71. 12
      packages/core/src/mods/toolBar/widgets/undo.tsx
  72. 54
      packages/core/src/mods/toolBar/widgets/verticalAlign.tsx
  73. 34
      packages/core/src/mods/toolBar/widgets/zoom.tsx
  74. 4
      packages/core/src/typings.d.ts
  75. 8
      packages/core/src/utils/flowChartUtils.ts
  76. 13
      packages/core/src/utils/index.ts
  77. 4
      packages/plugin-store/src/index.ts

22
.eslintrc.js

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

3
config/cli.jest.config.js

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

17
package-lock.json

@ -6936,10 +6936,9 @@
"dev": true "dev": true
}, },
"eventemitter3": { "eventemitter3": {
"version": "3.1.2", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", "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": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", "integrity": "sha1-Lem2j2Uo1WRO9cWVJqG0oHMGFp8="
"dev": true
}, },
"events": { "events": {
"version": "3.1.0", "version": "3.1.0",
@ -10172,6 +10171,14 @@
"dev": true, "dev": true,
"requires": { "requires": {
"eventemitter3": "^3.1.0" "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": { "p-reduce": {
@ -13021,4 +13028,4 @@
} }
} }
} }
} }

12
package.json

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

3
packages/cli/package.json

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

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

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

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

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

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

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

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

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

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

@ -2,36 +2,38 @@ const path = require('path');
const fs = require('fs-extra'); const fs = require('fs-extra');
const builtinDependencies = { const builtinDependencies = {
"eventemitter3": "^4.0.7" eventemitter3: '^4.0.7',
}; };
const extractDep = (dsl) => { const extractDep = (dsl) => {
const mergedDependencies = {}; const mergedDependencies = {};
const {cells = []} = dsl; const { cells = [] } = dsl;
cells.filter(cell => cell.shape !== 'edge').forEach(cell => { cells
const {dependencies} = cell.data || {}; .filter((cell) => cell.shape !== 'edge')
try { .forEach((cell) => {
const json = JSON.parse(dependencies); const { dependencies } = cell.data || {};
Object.keys(json).forEach(key => mergedDependencies[key] = json[key]); try {
} catch(err) { const json = JSON.parse(dependencies);
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; return mergedDependencies;
} };
const setup = async (dsl, projectRootPath) => { const setup = async (dsl, projectRootPath) => {
const pkgPath = path.join(projectRootPath, './package.json'); const pkgPath = path.join(projectRootPath, './package.json');
const pkgFile = await fs.readFile(pkgPath); const pkgFile = await fs.readFile(pkgPath);
const pkgJson = JSON.parse(pkgFile); const pkgJson = JSON.parse(pkgFile);
const dslDependencies = extractDep(dsl); const dslDependencies = extractDep(dsl);
if(!pkgJson.dependencies) { if (!pkgJson.dependencies) {
pkgJson.dependencies = dslDependencies; pkgJson.dependencies = dslDependencies;
} else { } else {
Object.keys(dslDependencies).forEach(key => { Object.keys(dslDependencies).forEach((key) => {
pkgJson.dependencies[key] = dslDependencies[key]; pkgJson.dependencies[key] = dslDependencies[key];
}); });
Object.keys(builtinDependencies).forEach(key => { Object.keys(builtinDependencies).forEach((key) => {
pkgJson.dependencies[key] = builtinDependencies[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 extractObj = (obj = {}, keys = []) => {
const ret = {}; const ret = {};
keys.forEach(key => ret[key] = obj[key]); keys.forEach((key) => (ret[key] = obj[key]));
return ret; return ret;
}; };
const simplify = (dsl) => { const simplify = (dsl) => {
const {cells = []} = dsl; const { cells = [] } = dsl;
return { return {
cells: cells.map(cell => { cells: cells.map((cell) => {
if(cell.shape === 'edge') { if (cell.shape === 'edge') {
return extractObj(cell, ['id', 'shape', 'source', 'target']); return extractObj(cell, ['id', 'shape', 'source', 'target']);
} else { } else {
return extractObj(cell, ['id', 'shape', 'data']); return extractObj(cell, ['id', 'shape', 'data']);
} }
}) }),
}; };
}; };

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

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

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

@ -3,5 +3,5 @@ const Init = require('./init');
module.exports = { module.exports = {
dev: Dev, 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'); const OUTPUT_FILE_PATH = path.join(process.cwd(), 'imove.config.js');
class Init extends Base { class Init extends Base {
getProjectName() { getProjectName() {
const pkgPath = path.join(this.projectPath, 'package.json'); const pkgPath = path.join(this.projectPath, 'package.json');
const pkgJson = fs.readJSONSync(pkgPath); 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_NAME = 'imove.config.js';
const CONFIG_FILE_PATH = path.join(process.cwd(), CONFIG_FILE_NAME); const CONFIG_FILE_PATH = path.join(process.cwd(), CONFIG_FILE_NAME);
const DEFAULT_CONFIG = { const DEFAULT_CONFIG = {
outputPath: path.join(process.cwd(), './src/logic/') outputPath: path.join(process.cwd(), './src/logic/'),
}; };
const mergeConfig = (config, DEFAULT_CONFIG) => { const mergeConfig = (config, DEFAULT_CONFIG) => {
// TODO: merge config // TODO: merge config
return config; return config;
} };
const getConfig = () => { const getConfig = () => {
const isConfigFileExisted = fs.existsSync(CONFIG_FILE_PATH); const isConfigFileExisted = fs.existsSync(CONFIG_FILE_PATH);
if(isConfigFileExisted) { if (isConfigFileExisted) {
return mergeConfig(require(CONFIG_FILE_PATH), DEFAULT_CONFIG); return mergeConfig(require(CONFIG_FILE_PATH), DEFAULT_CONFIG);
} else { } else {
return DEFAULT_CONFIG; 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 express = require('express');
const bodyParser = require('body-parser'); const bodyParser = require('body-parser');
const createServer = (port = 3500) => { const createServer = (port = 3500) => {
const app = express(); const app = express();
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false})); app.use(bodyParser.urlencoded({ extended: false }));
app.use((req, res, next) => { app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*"); res.header('Access-Control-Allow-Origin', '*');
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next(); next();
}); });
app.listen(port, () => { app.listen(port, () => {
@ -19,5 +17,5 @@ const createServer = (port = 3500) => {
}; };
module.exports = { module.exports = {
createServer createServer,
}; };

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

@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { message} from 'antd'; import { message } from 'antd';
export interface ILocalConfig { export interface ILocalConfig {
ip: string; ip: string;
@ -9,7 +9,7 @@ export interface ILocalConfig {
export enum ActionType { export enum ActionType {
create = 'create', create = 'create',
update = 'update', update = 'update',
remove = 'remove' remove = 'remove',
} }
export interface IModifyGraphAction { export interface IModifyGraphAction {
@ -21,16 +21,16 @@ export interface IModifyGraphAction {
interface RequestConfig { interface RequestConfig {
url: string; url: string;
method?: 'get' | 'post'; method?: 'get' | 'post';
params?: {[key: string]: any}; params?: { [key: string]: any };
headers?: {[key: string]: string}; headers?: { [key: string]: string };
} }
const request = (function() { const request = (function () {
const instance = axios.create(); const instance = axios.create();
instance.interceptors.response.use((response: any) => { instance.interceptors.response.use((response: any) => {
const {data} = response || {}; const { data } = response || {};
const {success, msg} = data || {}; const { success, msg } = data || {};
if(success) { if (success) {
return data; return data;
} else { } else {
message.error(msg); message.error(msg);
@ -38,60 +38,60 @@ const request = (function() {
} }
}); });
return (config: RequestConfig) => { return (config: RequestConfig) => {
const {url, method = 'post', params, headers = {}} = config; const { url, method = 'post', params, headers = {} } = config;
return instance.request({ return instance.request({
url, url,
method, method,
headers: { headers: {
'Content-Type': 'application/json;charset=utf-8', 'Content-Type': 'application/json;charset=utf-8',
...headers ...headers,
}, },
data: params, data: params,
timeout: 3000 timeout: 3000,
}); });
}; };
})(); })();
export const localConfig: ILocalConfig = { export const localConfig: ILocalConfig = {
ip: '127.0.0.1', ip: '127.0.0.1',
port: '3500' port: '3500',
} };
export const updateLocalConfig = (config: ILocalConfig) => { export const updateLocalConfig = (config: ILocalConfig) => {
localConfig.ip = config.ip || localConfig.ip; localConfig.ip = config.ip || localConfig.ip;
localConfig.port = config.port || localConfig.port; localConfig.port = config.port || localConfig.port;
} };
export const localConnect = () => { export const localConnect = () => {
return fetch(`http://${localConfig.ip}:${localConfig.port}/api/connect`, { return fetch(`http://${localConfig.ip}:${localConfig.port}/api/connect`, {
method: 'GET', method: 'GET',
headers: {'content-type': 'application/json'} headers: { 'content-type': 'application/json' },
}); });
} };
export const localSave = (data: any) => { export const localSave = (data: any) => {
fetch(`http://${localConfig.ip}:${localConfig.port}/api/save`, { fetch(`http://${localConfig.ip}:${localConfig.port}/api/save`, {
method: 'POST', method: 'POST',
headers: {'content-type': 'application/json'}, headers: { 'content-type': 'application/json' },
body: JSON.stringify(data) body: JSON.stringify(data),
}); });
} };
export const queryGraph = (projectId: string) => { export const queryGraph = (projectId: string) => {
return request({ return request({
url: '/api/queryGraph', url: '/api/queryGraph',
params: { params: {
projectId projectId,
} },
}); });
} };
export const modifyGraph = (projectId: string, actions: IModifyGraphAction[]) => { export const modifyGraph = (projectId: string, actions: IModifyGraphAction[]) => {
return request({ return request({
url: '/api/modifyGraph', url: '/api/modifyGraph',
params: { params: {
projectId, 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 = { const schema = {
base: Shape.Rect, base: Shape.Rect,
@ -9,11 +9,11 @@ const schema = {
attrs: { attrs: {
body: { body: {
fill: '#AACCF7', fill: '#AACCF7',
stroke: '#5E9CEE' stroke: '#5E9CEE',
}, },
label: { label: {
fill: '#333' fill: '#333',
} },
}, },
ports: { ports: {
groups: { groups: {
@ -25,7 +25,7 @@ const schema = {
magnet: true, magnet: true,
stroke: '#666', stroke: '#666',
strokeWidth: 1, strokeWidth: 1,
fill: '#fff' fill: '#fff',
}, },
}, },
}, },
@ -37,7 +37,7 @@ const schema = {
magnet: true, magnet: true,
stroke: '#666', stroke: '#666',
strokeWidth: 1, strokeWidth: 1,
fill: '#fff' fill: '#fff',
}, },
}, },
}, },
@ -49,7 +49,7 @@ const schema = {
magnet: true, magnet: true,
stroke: '#666', stroke: '#666',
strokeWidth: 1, strokeWidth: 1,
fill: '#fff' fill: '#fff',
}, },
}, },
}, },
@ -61,7 +61,7 @@ const schema = {
magnet: true, magnet: true,
stroke: '#666', stroke: '#666',
strokeWidth: 1, strokeWidth: 1,
fill: '#fff' fill: '#fff',
}, },
}, },
}, },
@ -69,29 +69,29 @@ const schema = {
items: [ items: [
{ {
id: 'top', id: 'top',
group: 'top' group: 'top',
}, },
{ {
id: 'right', id: 'right',
group: 'right' group: 'right',
}, },
{ {
id: 'bottom', id: 'bottom',
group: 'bottom' group: 'bottom',
}, },
{ {
id: 'left', id: 'left',
group: 'left' group: 'left',
} },
] ],
}, },
data: { data: {
label: '处理', label: '处理',
configSchema: '{\n \n}', configSchema: '{\n \n}',
configData: {}, configData: {},
dependencies: '{\n \n}', dependencies: '{\n \n}',
code: 'export default async function(ctx) {\n \n}' code: 'export default async function(ctx) {\n \n}',
} },
}; };
export default schema; 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 = { const schema = {
base: Shape.Polygon, base: Shape.Polygon,
@ -10,7 +10,7 @@ const schema = {
strokeWidth: 2, strokeWidth: 2,
stroke: '#333', stroke: '#333',
fill: '#FFFC7C', fill: '#FFFC7C',
refPoints: '0,10 10,0 20,10 10,20' refPoints: '0,10 10,0 20,10 10,20',
}, },
label: { label: {
refX: 0.5, refX: 0.5,
@ -19,8 +19,8 @@ const schema = {
fill: '#333', fill: '#333',
fontSize: 14, fontSize: 14,
textAnchor: 'middle', textAnchor: 'middle',
textVerticalAnchor: 'middle' textVerticalAnchor: 'middle',
} },
}, },
ports: { ports: {
groups: { groups: {
@ -32,7 +32,7 @@ const schema = {
magnet: true, magnet: true,
stroke: '#666', stroke: '#666',
strokeWidth: 1, strokeWidth: 1,
fill: '#fff' fill: '#fff',
}, },
}, },
}, },
@ -45,12 +45,12 @@ const schema = {
magnet: true, magnet: true,
stroke: '#666', stroke: '#666',
strokeWidth: 1, strokeWidth: 1,
fill: '#fff' fill: '#fff',
}, },
}, },
label: { label: {
position: 'right' position: 'right',
} },
}, },
bottom: { bottom: {
zIndex: 999999999, zIndex: 999999999,
@ -61,12 +61,12 @@ const schema = {
magnet: true, magnet: true,
stroke: '#666', stroke: '#666',
strokeWidth: 1, strokeWidth: 1,
fill: '#fff' fill: '#fff',
}, },
}, },
label: { label: {
position: 'bottom' position: 'bottom',
} },
}, },
left: { left: {
position: 'left', position: 'left',
@ -76,7 +76,7 @@ const schema = {
magnet: true, magnet: true,
stroke: '#666', stroke: '#666',
strokeWidth: 1, strokeWidth: 1,
fill: '#fff' fill: '#fff',
}, },
}, },
}, },
@ -84,31 +84,31 @@ const schema = {
items: [ items: [
{ {
id: 'top', id: 'top',
group: 'top' group: 'top',
}, },
{ {
id: 'right', id: 'right',
group: 'right', group: 'right',
attrs: { attrs: {
text: { text: {
text: '是' text: '是',
} },
} },
}, },
{ {
id: 'bottom', id: 'bottom',
group: 'bottom', group: 'bottom',
attrs: { attrs: {
text: { text: {
text: '否' text: '否',
} },
} },
}, },
{ {
id: 'left', id: 'left',
group: 'left' group: 'left',
} },
] ],
}, },
data: { data: {
label: '判断', label: '判断',
@ -117,14 +117,14 @@ const schema = {
dependencies: '{\n \n}', dependencies: '{\n \n}',
ports: { ports: {
right: { right: {
condition: 'true' condition: 'true',
}, },
bottom: { 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; export default schema;

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

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

2
packages/core/src/common/const.ts

@ -1,3 +1,3 @@
export const MIN_ZOOM = 0.5; export const MIN_ZOOM = 0.5;
export const MAX_ZOOM = 1.5; export const MAX_ZOOM = 1.5;
export const ZOOM_STEP = 0.1; export const ZOOM_STEP = 0.1;

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

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

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

@ -2,10 +2,10 @@ import start from './start';
import branch from './branch'; import branch from './branch';
import behavior from './behavior'; import behavior from './behavior';
const cellSchemaMap: {[key: string]: any} = { const cellSchemaMap: { [key: string]: any } = {
'imove-start-preview': start, 'imove-start-preview': start,
'imove-branch-preview': branch, 'imove-branch-preview': branch,
'imove-behavior-preview': behavior 'imove-behavior-preview': behavior,
}; };
export default cellSchemaMap; 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 = { const schema = {
base: Shape.Circle, base: Shape.Circle,
@ -11,11 +11,11 @@ const schema = {
body: { body: {
strokeWidth: 0, strokeWidth: 0,
stroke: '#333', stroke: '#333',
fill: '#333' fill: '#333',
}, },
label: { label: {
fill: '#FFF' fill: '#FFF',
} },
}, },
}; };

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

@ -1,50 +1,50 @@
import {safeGet} from '../utils'; import { safeGet } from '../utils';
import {localSave} from '../api'; import { localSave } from '../api';
import {Cell, Edge, Graph, Node} from '@antv/x6'; import { Cell, Edge, Graph, Node } from '@antv/x6';
import {MIN_ZOOM, MAX_ZOOM, ZOOM_STEP} from './const'; import { MIN_ZOOM, MAX_ZOOM, ZOOM_STEP } from './const';
interface Shortcut { interface Shortcut {
keys: string | string[]; keys: string | string[];
handler: (flowChart: Graph) => void; handler: (flowChart: Graph) => void;
} }
const shortcuts: {[key: string]: Shortcut} = { const shortcuts: { [key: string]: Shortcut } = {
save: { save: {
keys: `meta + s`, keys: 'meta + s',
handler(flowChart: Graph) { handler(flowChart: Graph) {
localSave({dsl: flowChart.toJSON()}); localSave({ dsl: flowChart.toJSON() });
return false; return false;
} },
}, },
undo: { undo: {
keys: `meta + z`, keys: 'meta + z',
handler(flowChart: Graph) { handler(flowChart: Graph) {
flowChart.undo(); flowChart.undo();
return false; return false;
} },
}, },
redo: { redo: {
keys: `meta + shift + z`, keys: 'meta + shift + z',
handler(flowChart: Graph) { handler(flowChart: Graph) {
flowChart.redo(); flowChart.redo();
return false; return false;
} },
}, },
zoomIn: { zoomIn: {
keys: `meta + shift + \+`, keys: 'meta + shift + +',
handler(flowChart: Graph) { handler(flowChart: Graph) {
const nextZoom = (flowChart.zoom() + ZOOM_STEP).toPrecision(2); const nextZoom = (flowChart.zoom() + ZOOM_STEP).toPrecision(2);
flowChart.zoomTo(Number(nextZoom), {maxScale: MAX_ZOOM}); flowChart.zoomTo(Number(nextZoom), { maxScale: MAX_ZOOM });
return false; return false;
} },
}, },
zoomOut: { zoomOut: {
keys: `meta + shift + \-`, keys: 'meta + shift + -',
handler(flowChart: Graph) { handler(flowChart: Graph) {
const nextZoom = (flowChart.zoom() - ZOOM_STEP).toPrecision(2); const nextZoom = (flowChart.zoom() - ZOOM_STEP).toPrecision(2);
flowChart.zoomTo(Number(nextZoom), {minScale: MIN_ZOOM}); flowChart.zoomTo(Number(nextZoom), { minScale: MIN_ZOOM });
return false; return false;
} },
}, },
copy: { copy: {
keys: 'meta + c', keys: 'meta + c',
@ -54,18 +54,18 @@ const shortcuts: {[key: string]: Shortcut} = {
flowChart.copy(cells); flowChart.copy(cells);
} }
return false; return false;
} },
}, },
paste: { paste: {
keys: 'meta + v', keys: 'meta + v',
handler(flowChart: Graph) { handler(flowChart: Graph) {
if (!flowChart.isClipboardEmpty()) { if (!flowChart.isClipboardEmpty()) {
const cells = flowChart.paste({offset: 32}); const cells = flowChart.paste({ offset: 32 });
flowChart.cleanSelection(); flowChart.cleanSelection();
flowChart.select(cells); flowChart.select(cells);
} }
return false; return false;
} },
}, },
delete: { delete: {
keys: ['backspace', 'del'], keys: ['backspace', 'del'],
@ -73,17 +73,17 @@ const shortcuts: {[key: string]: Shortcut} = {
const toDelCells = flowChart.getSelectedCells(); const toDelCells = flowChart.getSelectedCells();
const onEdgeDel = (edge: Edge) => { const onEdgeDel = (edge: Edge) => {
const srcNode = edge.getSourceNode() as Node; const srcNode = edge.getSourceNode() as Node;
const isSrcNodeInDelCells = !!toDelCells.find(c => c === srcNode); const isSrcNodeInDelCells = !!toDelCells.find((c) => c === srcNode);
if(srcNode && srcNode.shape === 'imove-branch' && !isSrcNodeInDelCells) { if (srcNode && srcNode.shape === 'imove-branch' && !isSrcNodeInDelCells) {
const portId = edge.getSourcePortId(); const portId = edge.getSourcePortId();
if(portId === 'right' || portId === 'bottom') { if (portId === 'right' || portId === 'bottom') {
const edgeLabel = safeGet(edge.getLabelAt(0), 'attrs.label.text', ''); const edgeLabel = safeGet(edge.getLabelAt(0), 'attrs.label.text', '');
srcNode.setPortProp(portId, 'attrs/text/text', edgeLabel); srcNode.setPortProp(portId, 'attrs/text/text', edgeLabel);
} }
} }
}; };
toDelCells.forEach((cell: Cell) => { toDelCells.forEach((cell: Cell) => {
if(cell.isEdge()) { if (cell.isEdge()) {
onEdgeDel(cell); onEdgeDel(cell);
} else { } else {
flowChart.getConnectedEdges(cell).forEach(onEdgeDel); flowChart.getConnectedEdges(cell).forEach(onEdgeDel);
@ -92,14 +92,14 @@ const shortcuts: {[key: string]: Shortcut} = {
flowChart.removeCells(flowChart.getSelectedCells()); flowChart.removeCells(flowChart.getSelectedCells());
flowChart.trigger('toolBar:forceUpdate'); flowChart.trigger('toolBar:forceUpdate');
return false; return false;
} },
}, },
selectAll: { selectAll: {
keys: 'meta + a', keys: 'meta + a',
handler(flowChart: Graph) { handler(flowChart: Graph) {
flowChart.select(flowChart.getCells()); flowChart.select(flowChart.getCells());
return false; return false;
} },
}, },
bold: { bold: {
keys: 'meta + b', keys: 'meta + b',
@ -108,41 +108,42 @@ const shortcuts: {[key: string]: Shortcut} = {
if (cells.length > 0) { if (cells.length > 0) {
const isAlreadyBold = safeGet(cells, '0.attrs.label.fontWeight', 'normal') === 'bold'; const isAlreadyBold = safeGet(cells, '0.attrs.label.fontWeight', 'normal') === 'bold';
cells.forEach((cell) => { cells.forEach((cell) => {
cell.setAttrs({label: {fontWeight: isAlreadyBold ? 'normal' : 'bold'}}); cell.setAttrs({ label: { fontWeight: isAlreadyBold ? 'normal' : 'bold' } });
}); });
flowChart.trigger('toolBar:forceUpdate'); flowChart.trigger('toolBar:forceUpdate');
} }
return false; return false;
} },
}, },
italic: { italic: {
keys: 'meta + i', keys: 'meta + i',
handler(flowChart: Graph) { handler(flowChart: Graph) {
const cells = flowChart.getSelectedCells(); const cells = flowChart.getSelectedCells();
if(cells.length > 0) { if (cells.length > 0) {
const isAlreadyItalic = safeGet(cells, '0.attrs.label.fontStyle', 'normal') === 'italic'; const isAlreadyItalic = safeGet(cells, '0.attrs.label.fontStyle', 'normal') === 'italic';
cells.forEach((cell) => { cells.forEach((cell) => {
cell.setAttrs({label: {fontStyle: isAlreadyItalic ? 'normal' : 'italic'}}); cell.setAttrs({ label: { fontStyle: isAlreadyItalic ? 'normal' : 'italic' } });
}); });
flowChart.trigger('toolBar:forceUpdate'); flowChart.trigger('toolBar:forceUpdate');
} }
return false; return false;
} },
}, },
underline: { underline: {
keys: 'meta + u', keys: 'meta + u',
handler(flowChart: Graph) { handler(flowChart: Graph) {
const cells = flowChart.getSelectedCells(); const cells = flowChart.getSelectedCells();
if(cells.length > 0) { if (cells.length > 0) {
const isAlreadyUnderline = safeGet(cells, '0.attrs.label.textDecoration', 'none') === 'underline'; const isAlreadyUnderline =
safeGet(cells, '0.attrs.label.textDecoration', 'none') === 'underline';
cells.forEach((cell) => { cells.forEach((cell) => {
cell.setAttrs({label: {textDecoration: isAlreadyUnderline ? 'none' : 'underline'}}); cell.setAttrs({ label: { textDecoration: isAlreadyUnderline ? 'none' : 'underline' } });
}); });
flowChart.trigger('toolBar:forceUpdate'); flowChart.trigger('toolBar:forceUpdate');
} }
return false; return false;
} },
} },
}; };
export default shortcuts; 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 { interface IProps {
color: string; color: string;
@ -8,8 +8,7 @@ interface IProps {
} }
const ColorPicker: React.FC<IProps> = (props) => { const ColorPicker: React.FC<IProps> = (props) => {
const { color, onChangeComplete: changeCb } = props;
const {color, onChangeComplete: changeCb} = props;
const [curColor, setCurColor] = useState(color); const [curColor, setCurColor] = useState(color);
// life // life
@ -19,13 +18,7 @@ const ColorPicker: React.FC<IProps> = (props) => {
const onChange = (color: ColorResult): void => setCurColor(color.hex); const onChange = (color: ColorResult): void => setCurColor(color.hex);
const onChangeComplete = (color: ColorResult): void => changeCb(color.hex); const onChangeComplete = (color: ColorResult): void => changeCb(color.hex);
return ( return <SketchPicker color={curColor} onChange={onChange} onChangeComplete={onChangeComplete} />;
<SketchPicker
color={curColor}
onChange={onChange}
onChangeComplete={onChangeComplete}
/>
);
}; };
export default ColorPicker; 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 { class MiniMapSimpleNode extends NodeView {
protected renderMarkup() { protected renderMarkup() {
return this.renderJSONMarkup({ return this.renderJSONMarkup({
tagName: 'rect', tagName: 'rect',
@ -19,7 +18,7 @@ class MiniMapSimpleNode extends NodeView {
refWidth: '100%', refWidth: '100%',
refHeight: '100%', refHeight: '100%',
fill: '#D9D9D9', fill: '#D9D9D9',
} },
}); });
} }
} }

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

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

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

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

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

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

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

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

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

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

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

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

@ -2,21 +2,21 @@ import React from 'react';
import styles from './index.module.less'; import styles from './index.module.less';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
import ConnectStatus from './connectStatus'; import ConnectStatus from './connectStatus';
interface IProps { interface IProps {
flowChart: Graph; flowChart: Graph;
} }
const Header: React.FC<IProps> = props => { const Header: React.FC<IProps> = (props) => {
const {flowChart} = props; const { flowChart } = props;
return ( return (
<div className={styles.container}> <div className={styles.container}>
<span className={styles.titleText}>iMove</span> <span className={styles.titleText}>iMove</span>
<ConnectStatus flowChart={flowChart}/> <ConnectStatus flowChart={flowChart} />
</div> </div>
); );
} };
export default Header; 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 styles from './index.module.less';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
interface ISubComponentProps { interface ISubComponentProps {
flowChart: Graph; flowChart: Graph;
@ -17,35 +17,26 @@ interface IProps {
} }
const Layout: React.FC<IProps> = (props) => { 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; let header, sideBar, toolBar, settingBar;
if(flowChart) { if (flowChart) {
header = <Header flowChart={flowChart}/>; header = <Header flowChart={flowChart} />;
sideBar = <SideBar flowChart={flowChart}/>; sideBar = <SideBar flowChart={flowChart} />;
toolBar = <ToolBar flowChart={flowChart}/>; toolBar = <ToolBar flowChart={flowChart} />;
settingBar = <SettingBar flowChart={flowChart}/>; settingBar = <SettingBar flowChart={flowChart} />;
} }
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.header}> <div className={styles.header}>{header}</div>
{header}
</div>
<div className={styles.body}> <div className={styles.body}>
<div className={styles.sideBar}> <div className={styles.sideBar}>{sideBar}</div>
{sideBar}
</div>
<div className={styles.full}> <div className={styles.full}>
<div className={styles.toolBar}> <div className={styles.toolBar}>{toolBar}</div>
{toolBar}
</div>
{props.children} {props.children}
</div> </div>
<div className={styles.settingBar}> <div className={styles.settingBar}>{settingBar}</div>
{settingBar}
</div>
</div> </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 'antd/es/checkbox/style';
import styles from './index.module.less'; import styles from './index.module.less';
import {Checkbox as AntdCheckbox} from 'antd'; import { Checkbox as AntdCheckbox } from 'antd';
import {CheckboxChangeEvent} from 'antd/lib/checkbox'; import { CheckboxChangeEvent } from 'antd/lib/checkbox';
interface IProps { interface IProps {
value: any; value: any;
@ -15,18 +15,18 @@ interface IProps {
onValueChange: (val: boolean) => void; onValueChange: (val: boolean) => void;
} }
const Checkbox: React.FC<IProps> = props => { const Checkbox: React.FC<IProps> = (props) => {
const {name, title, value, disabled, onValueChange} = props; const { name, title, value, disabled, onValueChange } = props;
const onChange = (evt: CheckboxChangeEvent): void => { const onChange = (evt: CheckboxChangeEvent): void => {
console.log(evt.target.checked) console.log(evt.target.checked);
if(evt.target.checked !== value) { if (evt.target.checked !== value) {
onValueChange(evt.target.checked); onValueChange(evt.target.checked);
} }
}; };
return ( return (
<div className={styles.container}> <div className={styles.container}>
<p className={styles.titleText}>{title}</p> <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> </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/modal/style';
import 'antd/es/button/style'; import 'antd/es/button/style';
import styles from './index.module.less'; import styles from './index.module.less';
import AceEditor from 'react-ace'; 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/theme-dracula';
import 'ace-builds/src-noconflict/mode-javascript'; import 'ace-builds/src-noconflict/mode-javascript';
import 'ace-builds/src-noconflict/snippets/javascript'; import 'ace-builds/src-noconflict/snippets/javascript';
@ -18,9 +18,8 @@ interface IProps {
onValueChange: (value: string) => void; onValueChange: (value: string) => void;
} }
const Code: React.FC<IProps> = props => { const Code: React.FC<IProps> = (props) => {
const { title, value, onValueChange } = props;
const {title, value, onValueChange} = props;
const [visible, setVisible] = useState<boolean>(false); const [visible, setVisible] = useState<boolean>(false);
// events // events
@ -38,11 +37,7 @@ const Code: React.FC<IProps> = props => {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<p className={styles.titleText}>{title}</p> <p className={styles.titleText}>{title}</p>
<Button <Button block={true} className={styles.btn} onClick={onClickEdit}>
block={true}
className={styles.btn}
onClick={onClickEdit}
>
</Button> </Button>
<EditModal <EditModal
@ -66,23 +61,22 @@ interface IEditorModalProps {
const CODE_EDITOR_STYLE = { const CODE_EDITOR_STYLE = {
width: '100%', width: '100%',
height: 600 height: 600,
}; };
const CODE_EDITOR_OPTIONS = { const CODE_EDITOR_OPTIONS = {
fontSize: 14, fontSize: 14,
useWorker: false useWorker: false,
}; };
const EditModal: React.FC<IEditorModalProps> = props => { const EditModal: React.FC<IEditorModalProps> = (props) => {
const { visible, title, value, onOk, onCancel } = props;
const {visible, title, value, onOk, onCancel} = props;
const [code, setCode] = useState<string>(value); const [code, setCode] = useState<string>(value);
// life // life
useEffect(() => { useEffect(() => {
// set value when opening modal // set value when opening modal
// clear content when closing modal // clear content when closing modal
if(visible) { if (visible) {
setCode(value); setCode(value);
} else { } else {
setCode(''); setCode('');
@ -94,7 +88,7 @@ const EditModal: React.FC<IEditorModalProps> = props => {
onOk(code); onOk(code);
}; };
const onChangeCode = (newCode: string): void => { const onChangeCode = (newCode: string): void => {
if(newCode !== code) { if (newCode !== code) {
setCode(newCode); 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 'antd/es/input/style';
import styles from './index.module.less'; import styles from './index.module.less';
import {Input as AntdInput} from 'antd'; import { Input as AntdInput } from 'antd';
interface IProps { interface IProps {
value: any; value: any;
@ -14,17 +14,17 @@ interface IProps {
onValueChange: (val: string) => void; onValueChange: (val: string) => void;
} }
const Input: React.FC<IProps> = props => { const Input: React.FC<IProps> = (props) => {
const {name, title, value, disabled, onValueChange} = props; const { name, title, value, disabled, onValueChange } = props;
const onChange = (evt: React.ChangeEvent<HTMLInputElement>): void => { const onChange = (evt: React.ChangeEvent<HTMLInputElement>): void => {
if(evt.target.value !== value) { if (evt.target.value !== value) {
onValueChange(evt.target.value); onValueChange(evt.target.value);
} }
}; };
return ( return (
<div className={styles.container}> <div className={styles.container}>
<p className={styles.titleText}>{title}</p> <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> </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/modal/style';
import 'antd/es/button/style'; import 'antd/es/button/style';
@ -7,8 +7,8 @@ import styles from './index.module.less';
import AceEditor from 'react-ace'; import AceEditor from 'react-ace';
import JsonView from 'react-json-view'; import JsonView from 'react-json-view';
import {Button, Modal, message} from 'antd'; import { Button, Modal, message } from 'antd';
import {safeParse} from '../../../../utils'; import { safeParse } from '../../../../utils';
import 'ace-builds/src-noconflict/mode-json'; import 'ace-builds/src-noconflict/mode-json';
import 'ace-builds/src-noconflict/theme-dracula'; import 'ace-builds/src-noconflict/theme-dracula';
import 'ace-builds/src-noconflict/snippets/json'; import 'ace-builds/src-noconflict/snippets/json';
@ -21,9 +21,8 @@ interface IJsonProps {
onValueChange: (value: string) => void; onValueChange: (value: string) => void;
} }
const Json: React.FC<IJsonProps> = props => { const Json: React.FC<IJsonProps> = (props) => {
const { title, value, onValueChange } = props;
const {title, value, onValueChange} = props;
const [visible, setVisible] = useState<boolean>(false); const [visible, setVisible] = useState<boolean>(false);
// events // events
@ -49,11 +48,7 @@ const Json: React.FC<IJsonProps> = props => {
displayObjectSize={false} displayObjectSize={false}
src={safeParse(value)} src={safeParse(value)}
/> />
<Button <Button block={true} className={styles.btn} onClick={onClickEdit}>
block={true}
className={styles.btn}
onClick={onClickEdit}
>
</Button> </Button>
<EditModal <EditModal
@ -77,22 +72,21 @@ interface IEditorModalProps {
const CODE_EDITOR_STYLE = { const CODE_EDITOR_STYLE = {
width: '100%', width: '100%',
height: 400 height: 400,
}; };
const CODE_EDITOR_OPTIONS = { const CODE_EDITOR_OPTIONS = {
useWorker: false useWorker: false,
}; };
const EditModal: React.FC<IEditorModalProps> = props => { const EditModal: React.FC<IEditorModalProps> = (props) => {
const { visible, title, value, onOk, onCancel } = props;
const {visible, title, value, onOk, onCancel} = props;
const [json, setJson] = useState<string>(''); const [json, setJson] = useState<string>('');
// life // life
useEffect(() => { useEffect(() => {
// set value when opening modal // set value when opening modal
// clear content when closing modal // clear content when closing modal
if(visible) { if (visible) {
setJson(value); setJson(value);
} else { } else {
setJson(''); setJson('');
@ -104,13 +98,13 @@ const EditModal: React.FC<IEditorModalProps> = props => {
try { try {
JSON.parse(json); JSON.parse(json);
onOk(json); onOk(json);
} catch(err) { } catch (error) {
message.error('内容必须是合法json'); 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 => { const onChangeJson = (newJson: string): void => {
if(newJson !== json) { if (newJson !== json) {
setJson(newJson); 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/tabs/style';
import 'antd/es/empty/style'; import 'antd/es/empty/style';
import styles from './index.module.less'; import styles from './index.module.less';
import {Empty, Tabs} from 'antd'; import { Empty, Tabs } from 'antd';
import Basic from './mods/basic'; import Basic from './mods/basic';
import Config from './mods/config'; import Config from './mods/config';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
const {TabPane} = Tabs; const { TabPane } = Tabs;
interface IProps { interface IProps {
flowChart: Graph; flowChart: Graph;
} }
const SettingBar: React.FC<IProps> = props => { const SettingBar: React.FC<IProps> = (props) => {
const { flowChart } = props;
const {flowChart} = props; const forceUpdate = useReducer((n) => n + 1, 0)[1];
const forceUpdate = useReducer(n => n + 1, 0)[1];
useEffect(() => { useEffect(() => {
flowChart.on('settingBar:forceUpdate', forceUpdate); flowChart.on('settingBar:forceUpdate', forceUpdate);
@ -27,16 +26,20 @@ const SettingBar: React.FC<IProps> = props => {
}; };
}, []); }, []);
const nodes = flowChart.getSelectedCells().filter(v => v.shape !== 'edge'); const nodes = flowChart.getSelectedCells().filter((v) => v.shape !== 'edge');
if(nodes.length === 1) { if (nodes.length === 1) {
return ( return (
<div className={styles.container}> <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'}> <TabPane tab={'基础信息'} key={'basic'}>
<Basic selectedCell={nodes[0]}/> <Basic selectedCell={nodes[0]} />
</TabPane> </TabPane>
<TabPane tab={'投放配置'} key={'config'}> <TabPane tab={'投放配置'} key={'config'}>
<Config selectedCell={nodes[0]}/> <Config selectedCell={nodes[0]} />
</TabPane> </TabPane>
</Tabs> </Tabs>
</div> </div>
@ -44,7 +47,7 @@ const SettingBar: React.FC<IProps> = props => {
} else { } else {
return ( return (
<div className={`${styles.container} ${styles.center}`}> <div className={`${styles.container} ${styles.center}`}>
<Empty description={'请选择一个节点'} image={Empty.PRESENTED_IMAGE_SIMPLE}/> <Empty description={'请选择一个节点'} image={Empty.PRESENTED_IMAGE_SIMPLE} />
</div> </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 styles from './index.module.less';
import {Cell} from '@antv/x6'; import { Cell } from '@antv/x6';
import Json from '../../components/json'; import Json from '../../components/json';
import Code from '../../components/code'; import Code from '../../components/code';
import Input from '../../components/input'; import Input from '../../components/input';
@ -19,11 +19,10 @@ interface IBasicData {
configSchema: string; configSchema: string;
} }
const Basic: React.FC<IProps> = props => { const Basic: React.FC<IProps> = (props) => {
const { selectedCell } = props;
const {selectedCell} = props;
const [data, setData] = useState<IBasicData>(); const [data, setData] = useState<IBasicData>();
const {label, code, trigger, dependencies, configSchema} = data || {}; const { label, code, trigger, dependencies, configSchema } = data || {};
// life // life
useEffect(() => { useEffect(() => {
@ -32,16 +31,16 @@ const Basic: React.FC<IProps> = props => {
// events // events
const commonChange = (key: string, val: string): void => { const commonChange = (key: string, val: string): void => {
selectedCell.setData({[key]: val}); selectedCell.setData({ [key]: val });
setData(Object.assign({}, data, {[key]: val})); setData(Object.assign({}, data, { [key]: val }));
}; };
const onChangeLabel = (val: string): void => { const onChangeLabel = (val: string): void => {
commonChange('label', val); commonChange('label', val);
selectedCell.setAttrs({label: {text: val}}); selectedCell.setAttrs({ label: { text: val } });
}; };
const onChangeConfigSchema = (val: string): void => { const onChangeConfigSchema = (val: string): void => {
commonChange('configSchema', val); commonChange('configSchema', val);
selectedCell.trigger('change:configSchema', {configSchema: val}); selectedCell.trigger('change:configSchema', { configSchema: val });
}; };
const onChangeCode = (val: string): void => commonChange('code', val); const onChangeCode = (val: string): void => commonChange('code', val);
const onChangeTrigger = (val: string): void => commonChange('trigger', val); const onChangeTrigger = (val: string): void => commonChange('trigger', val);
@ -49,12 +48,7 @@ const Basic: React.FC<IProps> = props => {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<Input <Input name={'label'} title={'显示名称'} value={label} onValueChange={onChangeLabel} />
name={'label'}
title={'显示名称'}
value={label}
onValueChange={onChangeLabel}
/>
{selectedCell.shape === 'imove-start' && ( {selectedCell.shape === 'imove-start' && (
<Input <Input
name={'trigger'} name={'trigger'}
@ -63,12 +57,7 @@ const Basic: React.FC<IProps> = props => {
onValueChange={onChangeTrigger} onValueChange={onChangeTrigger}
/> />
)} )}
<Code <Code name={'code'} title={'代码'} value={code} onValueChange={onChangeCode} />
name={'code'}
title={'代码'}
value={code}
onValueChange={onChangeCode}
/>
<Json <Json
name={'configSchema'} name={'configSchema'}
title={'投放配置schema'} 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 styles from './index.module.less';
import {Cell} from '@antv/x6'; import { Cell } from '@antv/x6';
import {Button ,Empty} from 'antd'; import { Button, Empty } from 'antd';
import Input from '../../components/input'; import Input from '../../components/input';
import Checkbox from '../../components/checkbox'; import Checkbox from '../../components/checkbox';
import {safeParse} from '../../../../utils'; import { safeParse } from '../../../../utils';
// FIXME // FIXME
// save original config in tempConfig when change to edit mode // save original config in tempConfig when change to edit mode
@ -15,7 +15,7 @@ let tempConfig: any = null;
enum Mode { enum Mode {
edit = 'edit', edit = 'edit',
readOnly = 'readOnly' readOnly = 'readOnly',
} }
interface IConfig { interface IConfig {
@ -26,19 +26,18 @@ interface IProps {
selectedCell: Cell; selectedCell: Cell;
} }
const Config: React.FC<IProps> = props => { const Config: React.FC<IProps> = (props) => {
const { selectedCell } = props;
const {selectedCell} = props;
const [mode, setMode] = useState<Mode>(Mode.readOnly); const [mode, setMode] = useState<Mode>(Mode.readOnly);
const [configData, setConfigData] = useState<IConfig>({}); const [configData, setConfigData] = useState<IConfig>({});
const [configSchema, setConfigSchema] = useState<IConfig>({}); const [configSchema, setConfigSchema] = useState<IConfig>({});
// life // life
useEffect(() => { useEffect(() => {
const {configSchema, configData = {}} = selectedCell.getData() || {}; const { configSchema, configData = {} } = selectedCell.getData() || {};
setConfigData(configData); setConfigData(configData);
setConfigSchema(safeParse(configSchema)); setConfigSchema(safeParse(configSchema));
selectedCell.on('change:configSchema', (data: {configSchema: string}) => { selectedCell.on('change:configSchema', (data: { configSchema: string }) => {
setConfigSchema(safeParse(data.configSchema)); setConfigSchema(safeParse(data.configSchema));
}); });
return () => { return () => {
@ -48,7 +47,7 @@ const Config: React.FC<IProps> = props => {
// events // events
const onFieldValueChange = (key: string, value: any) => { const onFieldValueChange = (key: string, value: any) => {
setConfigData(Object.assign({}, configData, {[key]: value})); setConfigData(Object.assign({}, configData, { [key]: value }));
}; };
const onClickEdit = (): void => { const onClickEdit = (): void => {
setMode(Mode.edit); setMode(Mode.edit);
@ -60,11 +59,11 @@ const Config: React.FC<IProps> = props => {
}; };
const onClickSave = (): void => { const onClickSave = (): void => {
setMode(Mode.readOnly); setMode(Mode.readOnly);
selectedCell.setData({configData}); selectedCell.setData({ configData });
}; };
// no config schema // no config schema
if(!configSchema || Object.keys(configSchema).length === 0) { if (!configSchema || Object.keys(configSchema).length === 0) {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<Empty <Empty
@ -78,7 +77,7 @@ const Config: React.FC<IProps> = props => {
return ( return (
<div className={styles.container}> <div className={styles.container}>
{Object.keys(configSchema).map((key, idx) => { {Object.keys(configSchema).map((key, idx) => {
const {title, description, type} = configSchema[key]; const { title, description, type } = configSchema[key];
const FieldComponent = Helper.getFieldComponent(type); const FieldComponent = Helper.getFieldComponent(type);
return ( return (
<FieldComponent <FieldComponent
@ -94,12 +93,16 @@ const Config: React.FC<IProps> = props => {
})} })}
{mode === Mode.readOnly ? ( {mode === Mode.readOnly ? (
<div className={styles.footContainer}> <div className={styles.footContainer}>
<Button block onClick={onClickEdit}></Button> <Button block onClick={onClickEdit}>
</Button>
</div> </div>
) : ( ) : (
<div className={styles.footContainer}> <div className={styles.footContainer}>
<Button onClick={onClickCancel}></Button> <Button onClick={onClickCancel}></Button>
<Button className={styles.saveBtn} type={'primary'} onClick={onClickSave}></Button> <Button className={styles.saveBtn} type={'primary'} onClick={onClickSave}>
</Button>
</div> </div>
)} )}
</div> </div>
@ -109,12 +112,12 @@ const Config: React.FC<IProps> = props => {
const Helper = { const Helper = {
getFieldComponent(type: string) { getFieldComponent(type: string) {
if(type === 'boolean') { if (type === 'boolean') {
return Checkbox; return Checkbox;
} else { } else {
return Input; return Input;
} }
} },
} };
export default Config; 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 'antd/es/collapse/style';
import styles from './index.module.less'; import styles from './index.module.less';
import {Collapse} from 'antd'; import { Collapse } from 'antd';
import {Addon, Events, Graph, Node} from '@antv/x6'; import { Addon, Events, Graph, Node } from '@antv/x6';
import previewCellSchemaMap from '../../common/previewCell'; import previewCellSchemaMap from '../../common/previewCell';
const {Dnd} = Addon; const { Dnd } = Addon;
const {Panel} = Collapse; const { Panel } = Collapse;
const CELL_SIZE = 80; const CELL_SIZE = 80;
const CELL_SCALE = 0.7; const CELL_SCALE = 0.7;
const GENERAL_GROUP = { const GENERAL_GROUP = {
key: 'general', key: 'general',
name: '通用元件', 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-start-preview': 'imove-start',
'imove-branch-preview': 'imove-branch', 'imove-branch-preview': 'imove-branch',
'imove-behavior-preview': 'imove-behavior', 'imove-behavior-preview': 'imove-behavior',
@ -32,9 +32,8 @@ interface ISideBarProps {
flowChart: Graph; flowChart: Graph;
} }
const SideBar: React.FC<ISideBarProps> = props => { const SideBar: React.FC<ISideBarProps> = (props) => {
const { flowChart } = props;
const {flowChart} = props;
const [dnd, setDnd] = useState<Addon.Dnd>(); const [dnd, setDnd] = useState<Addon.Dnd>();
const [groups, setGroups] = useState<IGroupItem[]>([]); const [groups, setGroups] = useState<IGroupItem[]>([]);
@ -42,16 +41,16 @@ const SideBar: React.FC<ISideBarProps> = props => {
useEffect(() => { useEffect(() => {
// TODO: fetch to get custom group data // TODO: fetch to get custom group data
setGroups([GENERAL_GROUP]); setGroups([GENERAL_GROUP]);
setDnd(new Dnd({target: flowChart, scaled: true})); setDnd(new Dnd({ target: flowChart, scaled: true }));
}, []); }, []);
return ( return (
<div className={styles.container}> <div className={styles.container}>
{dnd && ( {dnd && (
<Collapse className={styles.collapse} defaultActiveKey={['general', 'custom']}> <Collapse className={styles.collapse} defaultActiveKey={['general', 'custom']}>
{groups.map(group => ( {groups.map((group) => (
<Panel key={group.key} header={group.name}> <Panel key={group.key} header={group.name}>
<PanelContent dnd={dnd} cells={group.cells}/> <PanelContent dnd={dnd} cells={group.cells} />
</Panel> </Panel>
))} ))}
</Collapse> </Collapse>
@ -65,11 +64,11 @@ interface IPanelContentProps {
cells: string[]; cells: string[];
} }
const PanelContent: React.FC<IPanelContentProps> = props => { const PanelContent: React.FC<IPanelContentProps> = (props) => {
const {dnd, cells} = props; const { dnd, cells } = props;
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
if(ref.current) { if (ref.current) {
const graph = new Graph({ const graph = new Graph({
container: ref.current, container: ref.current,
width: ref.current.offsetWidth, width: ref.current.offsetWidth,
@ -80,25 +79,25 @@ const PanelContent: React.FC<IPanelContentProps> = props => {
cells.forEach((cell: any, index: number) => { cells.forEach((cell: any, index: number) => {
const rowIdx = Math.floor(index / 3); const rowIdx = Math.floor(index / 3);
const colIdx = index % 3; const colIdx = index % 3;
const {top} = previewCellSchemaMap[cell] || {}; const { top } = previewCellSchemaMap[cell] || {};
graph.addNode({ graph.addNode({
shape: cell, shape: cell,
x: colIdx * (CELL_SIZE + 16 / CELL_SCALE), 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.scale(CELL_SCALE, CELL_SCALE);
graph.on('cell:mousedown', (args: Events.EventArgs['cell:mousedown']) => { graph.on('cell:mousedown', (args: Events.EventArgs['cell:mousedown']) => {
const {node, e} = args; const { node, e } = args;
let newNode = node; let newNode = node;
if(SHAPE_REFLECT_MAP[node.shape]) { if (SHAPE_REFLECT_MAP[node.shape]) {
newNode = Node.create({shape: SHAPE_REFLECT_MAP[node.shape]}); newNode = Node.create({ shape: SHAPE_REFLECT_MAP[node.shape] });
} }
dnd.start(newNode, e); dnd.start(newNode, e);
}); });
} }
}, []); }, []);
return <div className={styles.chart} ref={ref}/>; return <div className={styles.chart} ref={ref} />;
}; };
export default SideBar; 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 'antd/es/tooltip/style';
import styles from './index.module.less'; import styles from './index.module.less';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
import widgets from './widgets'; import widgets from './widgets';
import ModifyStatus from './widgets/modifyStatus'; import ModifyStatus from './widgets/modifyStatus';
@ -11,10 +11,9 @@ interface IProps {
flowChart: Graph; flowChart: Graph;
} }
const ToolBar: React.FC<IProps> = props => { const ToolBar: React.FC<IProps> = (props) => {
const { flowChart } = props;
const {flowChart} = props; const forceUpdate = useReducer((n) => n + 1, 0)[1];
const forceUpdate = useReducer(n => n + 1, 0)[1];
useEffect(() => { useEffect(() => {
flowChart.on('toolBar:forceUpdate', forceUpdate); flowChart.on('toolBar:forceUpdate', forceUpdate);
@ -28,11 +27,11 @@ const ToolBar: React.FC<IProps> = props => {
{widgets.map((group, index) => ( {widgets.map((group, index) => (
<div key={index} className={styles.group}> <div key={index} className={styles.group}>
{group.map((ToolItem, index) => { {group.map((ToolItem, index) => {
return <ToolItem key={index} flowChart={flowChart}/>; return <ToolItem key={index} flowChart={flowChart} />;
})} })}
</div> </div>
))} ))}
<ModifyStatus flowChart={flowChart}/> <ModifyStatus flowChart={flowChart} />
</div> </div>
); );
}; };

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

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

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

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

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

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

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

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

16
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 React from 'react';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
import XIcon from '../../../components/xIcon'; import XIcon from '../../../components/xIcon';
import makeBtnWidget from './common/makeBtnWidget'; import makeBtnWidget from './common/makeBtnWidget';
import {getSelectedNodes, hasNodeSelected} from '../../../utils/flowChartUtils'; import { getSelectedNodes, hasNodeSelected } from '../../../utils/flowChartUtils';
interface IProps { interface IProps {
flowChart: Graph flowChart: Graph;
} }
const BringToTop: React.FC<IProps> = makeBtnWidget({ const BringToTop: React.FC<IProps> = makeBtnWidget({
tooltip: '置于顶层', tooltip: '置于顶层',
getIcon() { getIcon() {
return <XIcon type={'icon-bringtotop'}/>; return <XIcon type={'icon-bringtotop'} />;
}, },
handler(flowChart: Graph) { handler(flowChart: Graph) {
getSelectedNodes(flowChart).forEach(node => node.toFront()); getSelectedNodes(flowChart).forEach((node) => node.toFront());
}, },
disabled(flowChart: Graph) { disabled(flowChart: Graph) {
return !hasNodeSelected(flowChart); return !hasNodeSelected(flowChart);
} },
}); });
export default BringToTop; export default BringToTop;

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

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

@ -1,21 +1,21 @@
import React from 'react'; import React from 'react';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
import makeBtnWidget from './common/makeBtnWidget'; import makeBtnWidget from './common/makeBtnWidget';
import {FullscreenExitOutlined} from '@ant-design/icons'; import { FullscreenExitOutlined } from '@ant-design/icons';
interface IProps { interface IProps {
flowChart: Graph flowChart: Graph;
} }
const FitWindow: React.FC<IProps> = makeBtnWidget({ const FitWindow: React.FC<IProps> = makeBtnWidget({
tooltip: '适配窗口', tooltip: '适配窗口',
getIcon() { getIcon() {
return <FullscreenExitOutlined/>; return <FullscreenExitOutlined />;
}, },
handler(flowChart: Graph) { handler(flowChart: Graph) {
flowChart.zoomToFit({minScale: 0.5, maxScale: 1}); flowChart.zoomToFit({ minScale: 0.5, maxScale: 1 });
} },
}); });
export default FitWindow; 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 'antd/es/menu/style';
import {Menu} from 'antd'; import { Menu } from 'antd';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
import {safeGet} from '../../../utils'; import { safeGet } from '../../../utils';
import makeDropdownWidget from './common/makeDropdownWidget'; import makeDropdownWidget from './common/makeDropdownWidget';
interface IProps { interface IProps {
flowChart: Graph flowChart: Graph;
} }
const MenuItem = Menu.Item; const MenuItem = Menu.Item;
const FONT_SIZE_SET = [ const FONT_SIZE_SET = [9, 10, 11, 12, 13, 14, 15, 16, 19, 22, 24, 29, 32];
9, 10, 11, 12, 13, 14, 15, 16, 19, 22, 24, 29, 32
];
const FontSize: React.FC<IProps> = makeDropdownWidget({ const FontSize: React.FC<IProps> = makeDropdownWidget({
tooltip: '字号', tooltip: '字号',
getIcon(flowChart: Graph) { getIcon(flowChart: Graph) {
let fontSize = 14; let fontSize = 14;
const cells = flowChart.getSelectedCells(); const cells = flowChart.getSelectedCells();
if(cells.length > 0) { if (cells.length > 0) {
fontSize = safeGet(cells, '0.attrs.label.fontSize', 14); 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) { getOverlay(flowChart: Graph, onChange: (data: any) => void) {
return ( return (
<Menu onClick={(args) => onChange(args.key)}> <Menu onClick={(args) => onChange(args.key)}>
{FONT_SIZE_SET.map(fontSize => ( {FONT_SIZE_SET.map((fontSize) => (
<MenuItem key={fontSize}> <MenuItem key={fontSize}>
<span>{fontSize} px</span> <span>{fontSize} px</span>
</MenuItem> </MenuItem>
@ -38,7 +36,7 @@ const FontSize: React.FC<IProps> = makeDropdownWidget({
); );
}, },
handler: (flowChart: Graph, value: any) => { 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) { disabled(flowChart: Graph) {
return flowChart.getSelectedCellCount() === 0; 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 'antd/es/menu/style';
import {Menu} from 'antd'; import { Menu } from 'antd';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
import {safeGet} from '../../../utils'; import { safeGet } from '../../../utils';
import makeDropdownWidget from './common/makeDropdownWidget'; import makeDropdownWidget from './common/makeDropdownWidget';
import {hasNodeSelected, getSelectedNodes} from '../../../utils/flowChartUtils'; import { hasNodeSelected, getSelectedNodes } from '../../../utils/flowChartUtils';
import {AlignLeftOutlined, AlignCenterOutlined, AlignRightOutlined} from '@ant-design/icons'; import { AlignLeftOutlined, AlignCenterOutlined, AlignRightOutlined } from '@ant-design/icons';
interface IProps { interface IProps {
flowChart: Graph; flowChart: Graph;
@ -22,48 +22,48 @@ interface AlignItem {
textAnchor: string; textAnchor: string;
align: { align: {
horizontal: string; horizontal: string;
} };
} };
} }
const MenuItem = Menu.Item; const MenuItem = Menu.Item;
const ALIGN_MAP: {[key: string]: AlignItem} = { const ALIGN_MAP: { [key: string]: AlignItem } = {
left: { left: {
text: '左对齐', text: '左对齐',
icon: <AlignLeftOutlined/>, icon: <AlignLeftOutlined />,
attrs: { attrs: {
refX: 0, refX: 0,
refX2: 5, refX2: 5,
textAnchor: 'start', textAnchor: 'start',
align: { align: {
horizontal: 'left' horizontal: 'left',
} },
} },
}, },
center: { center: {
text: '居中对齐', text: '居中对齐',
icon: <AlignCenterOutlined/>, icon: <AlignCenterOutlined />,
attrs: { attrs: {
refX: 0.5, refX: 0.5,
refX2: 0, refX2: 0,
textAnchor: 'middle', textAnchor: 'middle',
align: { align: {
horizontal: 'center' horizontal: 'center',
} },
} },
}, },
right: { right: {
text: '右对齐', text: '右对齐',
icon: <AlignRightOutlined/>, icon: <AlignRightOutlined />,
attrs: { attrs: {
refX: 0.99, refX: 0.99,
refX2: -5, refX2: -5,
textAnchor: 'end', textAnchor: 'end',
align: { align: {
horizontal: 'right' horizontal: 'right',
} },
} },
} },
}; };
const HorizontalAlign: React.FC<IProps> = makeDropdownWidget({ const HorizontalAlign: React.FC<IProps> = makeDropdownWidget({
@ -71,14 +71,14 @@ const HorizontalAlign: React.FC<IProps> = makeDropdownWidget({
getIcon(flowChart: Graph) { getIcon(flowChart: Graph) {
let alignType = 'center'; let alignType = 'center';
const nodes = getSelectedNodes(flowChart); const nodes = getSelectedNodes(flowChart);
if(nodes.length > 0) { if (nodes.length > 0) {
alignType = safeGet(nodes, '0.attrs.label.align.horizontal', 'center'); alignType = safeGet(nodes, '0.attrs.label.align.horizontal', 'center');
} }
return ALIGN_MAP[alignType].icon; return ALIGN_MAP[alignType].icon;
}, },
getOverlay(flowChart: Graph, onChange: (data: any) => void) { getOverlay(flowChart: Graph, onChange: (data: any) => void) {
return ( return (
<Menu onClick={({key}) => onChange(ALIGN_MAP[key].attrs)}> <Menu onClick={({ key }) => onChange(ALIGN_MAP[key].attrs)}>
{Object.keys(ALIGN_MAP).map((alignType) => ( {Object.keys(ALIGN_MAP).map((alignType) => (
<MenuItem key={alignType}> <MenuItem key={alignType}>
{ALIGN_MAP[alignType].icon} {ALIGN_MAP[alignType].icon}
@ -89,7 +89,7 @@ const HorizontalAlign: React.FC<IProps> = makeDropdownWidget({
); );
}, },
handler: (flowChart: Graph, value: any) => { handler: (flowChart: Graph, value: any) => {
getSelectedNodes(flowChart).forEach(node => node.setAttrs({label: value})); getSelectedNodes(flowChart).forEach((node) => node.setAttrs({ label: value }));
}, },
disabled(flowChart: Graph) { disabled(flowChart: Graph) {
return !hasNodeSelected(flowChart); return !hasNodeSelected(flowChart);

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

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

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

@ -1,32 +1,32 @@
import React from 'react'; import React from 'react';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
import {safeGet} from '../../../utils'; import { safeGet } from '../../../utils';
import makeBtnWidget from './common/makeBtnWidget'; import makeBtnWidget from './common/makeBtnWidget';
import {ItalicOutlined} from '@ant-design/icons'; import { ItalicOutlined } from '@ant-design/icons';
import shortcuts from '../../../common/shortcuts'; import shortcuts from '../../../common/shortcuts';
interface IProps { interface IProps {
flowChart: Graph flowChart: Graph;
} }
const Italic: React.FC<IProps> = makeBtnWidget({ const Italic: React.FC<IProps> = makeBtnWidget({
tooltip: '斜体', tooltip: '斜体',
handler: shortcuts['italic'].handler, handler: shortcuts.italic.handler,
getIcon() { getIcon() {
return <ItalicOutlined/>; return <ItalicOutlined />;
}, },
disabled(flowChart: Graph) { disabled(flowChart: Graph) {
return flowChart.getSelectedCellCount() === 0; return flowChart.getSelectedCellCount() === 0;
}, },
selected(flowChart: Graph) { selected(flowChart: Graph) {
const cells = flowChart.getSelectedCells(); const cells = flowChart.getSelectedCells();
if(cells.length > 0) { if (cells.length > 0) {
return safeGet(cells, '0.attrs.label.fontStyle', 'normal') === 'italic'; return safeGet(cells, '0.attrs.label.fontStyle', 'normal') === 'italic';
} else { } else {
return false; return false;
} }
} },
}); });
export default Italic; 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 'antd/es/menu/style';
import {Menu} from 'antd'; import { Menu } from 'antd';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
import {safeGet} from '../../../utils'; import { safeGet } from '../../../utils';
import XIcon from '../../../components/xIcon'; import XIcon from '../../../components/xIcon';
import makeDropdownWidget from './common/makeDropdownWidget'; import makeDropdownWidget from './common/makeDropdownWidget';
import {hasEdgeSelected, getSelectedEdges} from '../../../utils/flowChartUtils'; import { hasEdgeSelected, getSelectedEdges } from '../../../utils/flowChartUtils';
interface IProps { interface IProps {
flowChart: Graph; flowChart: Graph;
@ -19,27 +19,27 @@ interface LineStyleItem {
attrs: { attrs: {
type: string; type: string;
strokeDasharray: string; strokeDasharray: string;
} };
} }
const MenuItem = Menu.Item; const MenuItem = Menu.Item;
const LINE_STYLE_MAP: {[key: string]: LineStyleItem} = { const LINE_STYLE_MAP: { [key: string]: LineStyleItem } = {
straight: { straight: {
text: '直线', text: '直线',
icon: <XIcon type={'icon-line'}/>, icon: <XIcon type={'icon-line'} />,
attrs: { attrs: {
type: 'straight', type: 'straight',
strokeDasharray: '5, 0' strokeDasharray: '5, 0',
} },
}, },
dashed: { dashed: {
text: '虚线', text: '虚线',
icon: <XIcon type={'icon-dash'}/>, icon: <XIcon type={'icon-dash'} />,
attrs: { attrs: {
type: 'dashed', type: 'dashed',
strokeDasharray: '5, 5' strokeDasharray: '5, 5',
} },
} },
}; };
const LineStyle: React.FC<IProps> = makeDropdownWidget({ const LineStyle: React.FC<IProps> = makeDropdownWidget({
@ -47,14 +47,14 @@ const LineStyle: React.FC<IProps> = makeDropdownWidget({
getIcon(flowChart: Graph) { getIcon(flowChart: Graph) {
let lineType = 'straight'; let lineType = 'straight';
const edges = getSelectedEdges(flowChart); const edges = getSelectedEdges(flowChart);
if(edges.length > 0) { if (edges.length > 0) {
lineType = safeGet(edges, '0.attrs.line.type', 'straight'); lineType = safeGet(edges, '0.attrs.line.type', 'straight');
} }
return LINE_STYLE_MAP[lineType].icon; return LINE_STYLE_MAP[lineType].icon;
}, },
getOverlay(flowChart: Graph, onChange: (data: any) => void) { getOverlay(flowChart: Graph, onChange: (data: any) => void) {
return ( 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) => ( {Object.keys(LINE_STYLE_MAP).map((lineType: string) => (
<MenuItem key={lineType}> <MenuItem key={lineType}>
{LINE_STYLE_MAP[lineType].icon} {LINE_STYLE_MAP[lineType].icon}
@ -65,7 +65,7 @@ const LineStyle: React.FC<IProps> = makeDropdownWidget({
); );
}, },
handler: (flowChart: Graph, value: any) => { handler: (flowChart: Graph, value: any) => {
getSelectedEdges(flowChart).forEach(edge => edge.setAttrs({line: value})); getSelectedEdges(flowChart).forEach((edge) => edge.setAttrs({ line: value }));
}, },
disabled(flowChart: Graph) { disabled(flowChart: Graph) {
return !hasEdgeSelected(flowChart); return !hasEdgeSelected(flowChart);

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

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

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

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

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

@ -1,20 +1,20 @@
import React from 'react'; import React from 'react';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
import {SaveOutlined} from '@ant-design/icons'; import { SaveOutlined } from '@ant-design/icons';
import shortcuts from '../../../common/shortcuts'; import shortcuts from '../../../common/shortcuts';
import makeBtnWidget from './common/makeBtnWidget'; import makeBtnWidget from './common/makeBtnWidget';
interface IProps { interface IProps {
flowChart: Graph flowChart: Graph;
} }
const Save: React.FC<IProps> = makeBtnWidget({ const Save: React.FC<IProps> = makeBtnWidget({
tooltip: '保存', tooltip: '保存',
handler: shortcuts['save'].handler, handler: shortcuts.save.handler,
getIcon() { getIcon() {
return <SaveOutlined/>; return <SaveOutlined />;
} },
}); });
export default Save; 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 'antd/es/menu/style';
import styles from './index.module.less'; import styles from './index.module.less';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
import {safeGet} from '../../../utils'; import { safeGet } from '../../../utils';
import XIcon from '../../../components/xIcon'; import XIcon from '../../../components/xIcon';
import ColorPicker from '../../../components/colorPicker'; import ColorPicker from '../../../components/colorPicker';
import makeDropdownWidget from './common/makeDropdownWidget'; import makeDropdownWidget from './common/makeDropdownWidget';
import {hasNodeSelected, getSelectedNodes} from '../../../utils/flowChartUtils'; import { hasNodeSelected, getSelectedNodes } from '../../../utils/flowChartUtils';
interface IProps { interface IProps {
flowChart: Graph; flowChart: Graph;
@ -19,7 +19,7 @@ const options = {
getCurTextColor(flowChart: Graph) { getCurTextColor(flowChart: Graph) {
let textColor = '#DDD'; let textColor = '#DDD';
const nodes = getSelectedNodes(flowChart); const nodes = getSelectedNodes(flowChart);
if(nodes.length > 0) { if (nodes.length > 0) {
textColor = safeGet(nodes, '0.attrs.label.fill', '#575757'); textColor = safeGet(nodes, '0.attrs.label.fill', '#575757');
} }
return textColor; return textColor;
@ -28,20 +28,18 @@ const options = {
const textColor = options.getCurTextColor(flowChart); const textColor = options.getCurTextColor(flowChart);
return ( return (
<div className={styles.textColorContainer}> <div className={styles.textColorContainer}>
<XIcon className={styles.textIcon} type={'icon-A'}/> <XIcon className={styles.textIcon} type={'icon-A'} />
<div className={styles.colorPreview} style={{backgroundColor: textColor}}/> <div className={styles.colorPreview} style={{ backgroundColor: textColor }} />
</div> </div>
); );
}, },
getOverlay(flowChart: Graph, onChange: (data: any) => void) { getOverlay(flowChart: Graph, onChange: (data: any) => void) {
const textColor = options.getCurTextColor(flowChart); const textColor = options.getCurTextColor(flowChart);
const onChangeComplete = (color: string) => onChange(color); const onChangeComplete = (color: string) => onChange(color);
return ( return <ColorPicker color={textColor} onChangeComplete={onChangeComplete} />;
<ColorPicker color={textColor} onChangeComplete={onChangeComplete}/>
);
}, },
handler: (flowChart: Graph, value: any) => { 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) { disabled(flowChart: Graph) {
return !hasNodeSelected(flowChart); return !hasNodeSelected(flowChart);

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

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

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

@ -1,23 +1,23 @@
import React from 'react'; import React from 'react';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
import {UndoOutlined} from '@ant-design/icons'; import { UndoOutlined } from '@ant-design/icons';
import shortcuts from '../../../common/shortcuts'; import shortcuts from '../../../common/shortcuts';
import makeBtnWidget from './common/makeBtnWidget'; import makeBtnWidget from './common/makeBtnWidget';
interface IProps { interface IProps {
flowChart: Graph flowChart: Graph;
} }
const Save: React.FC<IProps> = makeBtnWidget({ const Save: React.FC<IProps> = makeBtnWidget({
tooltip: '撤销', tooltip: '撤销',
handler: shortcuts['undo'].handler, handler: shortcuts.undo.handler,
getIcon() { getIcon() {
return <UndoOutlined/>; return <UndoOutlined />;
}, },
disabled(flowChart: Graph) { disabled(flowChart: Graph) {
return !flowChart.canUndo(); return !flowChart.canUndo();
} },
}); });
export default Save; 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 'antd/es/menu/style';
import {Menu} from 'antd'; import { Menu } from 'antd';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
import {safeGet} from '../../../utils'; import { safeGet } from '../../../utils';
import makeDropdownWidget from './common/makeDropdownWidget'; import makeDropdownWidget from './common/makeDropdownWidget';
import {hasNodeSelected, getSelectedNodes} from '../../../utils/flowChartUtils'; import { hasNodeSelected, getSelectedNodes } from '../../../utils/flowChartUtils';
import {VerticalAlignTopOutlined, VerticalAlignMiddleOutlined, VerticalAlignBottomOutlined} from '@ant-design/icons'; import {
VerticalAlignTopOutlined,
VerticalAlignMiddleOutlined,
VerticalAlignBottomOutlined,
} from '@ant-design/icons';
interface IProps { interface IProps {
flowChart: Graph; flowChart: Graph;
@ -22,48 +26,48 @@ interface AlignItem {
textVerticalAlign: string; textVerticalAlign: string;
align: { align: {
vertical: string; vertical: string;
} };
} };
} }
const MenuItem = Menu.Item; const MenuItem = Menu.Item;
const ALIGN_MAP: {[key: string]: AlignItem} = { const ALIGN_MAP: { [key: string]: AlignItem } = {
top: { top: {
text: '上对齐', text: '上对齐',
icon: <VerticalAlignTopOutlined/>, icon: <VerticalAlignTopOutlined />,
attrs: { attrs: {
refY: 0, refY: 0,
refY2: 10, refY2: 10,
textVerticalAlign: 'start', textVerticalAlign: 'start',
align: { align: {
vertical: 'top' vertical: 'top',
} },
} },
}, },
center: { center: {
text: '居中对齐', text: '居中对齐',
icon: <VerticalAlignMiddleOutlined/>, icon: <VerticalAlignMiddleOutlined />,
attrs: { attrs: {
refY: 0.5, refY: 0.5,
refY2: 0, refY2: 0,
textVerticalAlign: 'middle', textVerticalAlign: 'middle',
align: { align: {
vertical: 'center' vertical: 'center',
} },
} },
}, },
bottom: { bottom: {
text: '下对齐', text: '下对齐',
icon: <VerticalAlignBottomOutlined/>, icon: <VerticalAlignBottomOutlined />,
attrs: { attrs: {
refY: 0.99, refY: 0.99,
refY2: -10, refY2: -10,
textVerticalAlign: 'end', textVerticalAlign: 'end',
align: { align: {
vertical: 'bottom' vertical: 'bottom',
} },
} },
} },
}; };
const VerticalAlign: React.FC<IProps> = makeDropdownWidget({ const VerticalAlign: React.FC<IProps> = makeDropdownWidget({
@ -71,14 +75,14 @@ const VerticalAlign: React.FC<IProps> = makeDropdownWidget({
getIcon(flowChart: Graph) { getIcon(flowChart: Graph) {
let alignType = 'center'; let alignType = 'center';
const nodes = getSelectedNodes(flowChart); const nodes = getSelectedNodes(flowChart);
if(nodes.length > 0) { if (nodes.length > 0) {
alignType = safeGet(nodes, '0.attrs.label.align.vertical', 'center'); alignType = safeGet(nodes, '0.attrs.label.align.vertical', 'center');
} }
return ALIGN_MAP[alignType].icon; return ALIGN_MAP[alignType].icon;
}, },
getOverlay(flowChart: Graph, onChange: (data: any) => void) { getOverlay(flowChart: Graph, onChange: (data: any) => void) {
return ( return (
<Menu onClick={({key}) => onChange(ALIGN_MAP[key].attrs)}> <Menu onClick={({ key }) => onChange(ALIGN_MAP[key].attrs)}>
{Object.keys(ALIGN_MAP).map((alignType: string) => ( {Object.keys(ALIGN_MAP).map((alignType: string) => (
<MenuItem key={alignType}> <MenuItem key={alignType}>
{ALIGN_MAP[alignType].icon} {ALIGN_MAP[alignType].icon}
@ -89,7 +93,7 @@ const VerticalAlign: React.FC<IProps> = makeDropdownWidget({
); );
}, },
handler: (flowChart: Graph, value: any) => { handler: (flowChart: Graph, value: any) => {
getSelectedNodes(flowChart).forEach(node => node.setAttrs({label: value})); getSelectedNodes(flowChart).forEach((node) => node.setAttrs({ label: value }));
}, },
disabled(flowChart: Graph) { disabled(flowChart: Graph) {
return !hasNodeSelected(flowChart); 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 styles from './index.module.less';
import {Graph} from '@antv/x6'; import { Graph } from '@antv/x6';
import shortcuts from '../../../common/shortcuts'; import shortcuts from '../../../common/shortcuts';
import makeBtnWidget from './common/makeBtnWidget'; import makeBtnWidget from './common/makeBtnWidget';
import {ZoomOutOutlined, ZoomInOutlined} from '@ant-design/icons'; import { ZoomOutOutlined, ZoomInOutlined } from '@ant-design/icons';
interface IProps { interface IProps {
flowChart: Graph flowChart: Graph;
} }
const MIN_ZOOM = 0.5; const MIN_ZOOM = 0.5;
@ -16,28 +16,28 @@ const MAX_ZOOM = 1.5;
const ZoomOut: React.FC<IProps> = makeBtnWidget({ const ZoomOut: React.FC<IProps> = makeBtnWidget({
tooltip: '缩小', tooltip: '缩小',
handler: shortcuts['zoomOut'].handler, handler: shortcuts.zoomOut.handler,
getIcon() { getIcon() {
return <ZoomOutOutlined/>; return <ZoomOutOutlined />;
}, },
disabled(flowChart: Graph) { disabled(flowChart: Graph) {
return flowChart.zoom() <= MIN_ZOOM; return flowChart.zoom() <= MIN_ZOOM;
} },
}); });
const ZoomIn: React.FC<IProps> = makeBtnWidget({ const ZoomIn: React.FC<IProps> = makeBtnWidget({
tooltip: '放大', tooltip: '放大',
handler: shortcuts['zoomIn'].handler, handler: shortcuts.zoomIn.handler,
getIcon() { getIcon() {
return <ZoomInOutlined/>; return <ZoomInOutlined />;
}, },
disabled(flowChart: Graph) { disabled(flowChart: Graph) {
return flowChart.zoom() >= MAX_ZOOM; return flowChart.zoom() >= MAX_ZOOM;
} },
}); });
const Zoom: React.FC<IProps> = props => { const Zoom: React.FC<IProps> = (props) => {
const {flowChart} = props; const { flowChart } = props;
const [scale, setScale] = useState<number>(flowChart.zoom()); const [scale, setScale] = useState<number>(flowChart.zoom());
useEffect(() => { useEffect(() => {
flowChart.on('scale', () => { flowChart.on('scale', () => {
@ -46,11 +46,9 @@ const Zoom: React.FC<IProps> = props => {
}, [flowChart]); }, [flowChart]);
return ( return (
<div className={styles.zoomContainer}> <div className={styles.zoomContainer}>
<ZoomOut {...props}/> <ZoomOut {...props} />
<span className={styles.zoomText}> <span className={styles.zoomText}>{Helper.scaleFormatter(scale)}</span>
{Helper.scaleFormatter(scale)} <ZoomIn {...props} />
</span>
<ZoomIn {...props}/>
</div> </div>
); );
}; };
@ -58,7 +56,7 @@ const Zoom: React.FC<IProps> = props => {
const Helper = { const Helper = {
scaleFormatter(scale: number): string { scaleFormatter(scale: number): string {
return (scale * 100).toFixed(0) + '%'; return (scale * 100).toFixed(0) + '%';
} },
}; };
export default Zoom; export default Zoom;

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

@ -1,9 +1,9 @@
declare module '*.module.css' { declare module '*.module.css' {
const classes: {[key: string]: string}; const classes: { [key: string]: string };
export default classes; export default classes;
} }
declare module '*.less' { declare module '*.less' {
const classes: {[key: string]: string}; const classes: { [key: string]: string };
export default classes; 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 => { export const hasCellSelected = (flowChart: Graph): boolean => {
return flowChart.getSelectedCellCount() > 0; return flowChart.getSelectedCellCount() > 0;
} };
export const hasNodeSelected = (flowChart: Graph): boolean => { export const hasNodeSelected = (flowChart: Graph): boolean => {
return flowChart.getSelectedCells().filter((cell: Cell) => cell.isNode()).length > 0; 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[] => { export const getSelectedNodes = (flowChart: Graph): Cell[] => {
return flowChart.getSelectedCells().filter((cell: Cell) => cell.isNode()); return flowChart.getSelectedCells().filter((cell: Cell) => cell.isNode());
} };
export const getSelectedEdges = (flowChart: Graph): Cell[] => { export const getSelectedEdges = (flowChart: Graph): Cell[] => {
return flowChart.getSelectedCells().filter((cell: Cell) => cell.isEdge()); return flowChart.getSelectedCells().filter((cell: Cell) => cell.isEdge());
} };

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

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

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

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

Loading…
Cancel
Save