diff --git a/.gitignore b/.gitignore index 72b3f8225..d919991a0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ packages/*/dist/ package-lock.json yarn.lock deploy-space/packages +deploy-space/.env # IDE diff --git a/README.md b/README.md index 7b563a190..3007afc49 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,12 @@ #### 跑起来: - `npm run setup` +- `npm start` #### 开发提交: - `git add ` -- `npm run commit` +- `npm run commit` # 在根目录 ## 发布 diff --git a/deploy-space/html/index.html b/deploy-space/html/index.html index b3eeefd68..450c984b7 100644 --- a/deploy-space/html/index.html +++ b/deploy-space/html/index.html @@ -4,7 +4,7 @@ - LowCodeEngine DEMO + LowCodeEngine Editor DEMO @@ -16,7 +16,7 @@ - + @@ -24,6 +24,6 @@ - + diff --git a/deploy-space/html/preview.html b/deploy-space/html/preview.html index c8f2cfdcb..e37a87391 100644 --- a/deploy-space/html/preview.html +++ b/deploy-space/html/preview.html @@ -4,7 +4,8 @@ - LowCodeEngine DEMO + LowCodeEngine Preview DEMO + @@ -13,9 +14,10 @@ + -
+ diff --git a/deploy-space/package.json b/deploy-space/package.json index 661c105a7..37698f1dc 100644 --- a/deploy-space/package.json +++ b/deploy-space/package.json @@ -1,9 +1,5 @@ { "private": true, - "dependencies": { - "tslib": "^1.9.3", - "typescript": "^3.2.2" - }, "workspaces": { "packages": [ "packages/*" @@ -13,5 +9,8 @@ "**/@alife/theme-lowcode-*" ] }, - "engines" : { "node" : "^10" } + "dependencies": { + "tslib": "^1.11.1", + "typescript": "^3.8.3" + } } diff --git a/deploy-space/tsconfig.json b/deploy-space/tsconfig.json index 462d7443c..005c63fad 100644 --- a/deploy-space/tsconfig.json +++ b/deploy-space/tsconfig.json @@ -5,7 +5,7 @@ // Target latest version of ECMAScript. "target": "esnext", // Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. - "module": "commonjs", + "module": "esnext", // Search under node_modules for non-relative imports. "moduleResolution": "node", // Process & infer types from .js files. @@ -34,6 +34,7 @@ "resolveJsonModule": true, // skip type checking of declaration files "skipLibCheck": true, + "outDir": "lib" }, "exclude": ["**/test", "**/lib", "**/es", "node_modules"] } diff --git a/docs/code-specification.md b/docs/code-specification.md index 4c42cd0c7..0a7c9f555 100644 --- a/docs/code-specification.md +++ b/docs/code-specification.md @@ -10,7 +10,7 @@ - 使用 `camelCase` 为属性或本地变量命名 - 不要为私有属性名添加 `_` 前缀 - 尽可能使用完整的单词拼写命名 - - 文件夹命名统一使用小写 + - 文件夹/文件命名统一使用小写 `get-custom-data.ts` ### 组件 diff --git a/lerna.json b/lerna.json index 75c6500a9..a74ab4dc0 100644 --- a/lerna.json +++ b/lerna.json @@ -1,7 +1,7 @@ { "lerna": "2.11.0", "version": "independent", - "npmClient": "yarn", + "npmClient": "tyarn", "registry": "http://registry.npm.alibaba-inc.com", "useWorkspaces": true, "packages": [ diff --git a/package.json b/package.json index 60da5996d..30d7377c1 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,34 @@ { "private": true, + "workspaces": { + "packages": [ + "packages/*" + ], + "nohoist": [ + "**/css-modules-typescript-loader", + "**/@alife/theme-lowcode-*" + ] + }, "scripts": { + "build": "lerna run build --stream", "clean": "rm -rf ./packages/*/lib ./packages/*/es ./packages/*/dist ./packages/*/build", + "commit": "git-cz", + "pub": "lerna publish", "setup": "./scripts/setup.sh", "start": "./scripts/start.sh", - "build": "lerna run build", - "test": "lerna run test", - "test:snapshot": "lerna run test:snapshot", - "pub": "lerna publish", - "commit": "git-cz" + "test": "lerna run test --stream", + "test:snapshot": "lerna run test:snapshot" + }, + "lint-staged": { + "*.{tsx,ts}": [ + "eslint --quiet", + "git add" + ] + }, + "config": { + "commitizen": { + "path": "node_modules/cz-conventional-changelog" + } }, "devDependencies": { "@ali/lowcode-config": "^2.0.5", @@ -26,25 +46,7 @@ "tslib": "^1.9.3", "typescript": "^3.2.2" }, - "engines" : { "node" : "^10" }, - "workspaces": { - "packages": [ - "packages/*" - ], - "nohoist": [ - "**/css-modules-typescript-loader", - "**/@alife/theme-lowcode-*" - ] - }, - "lint-staged": { - "*.{tsx,ts}": [ - "eslint --quiet", - "git add" - ] - }, - "config": { - "commitizen": { - "path": "node_modules/cz-conventional-changelog" - } + "engines": { + "node": ">=10.0.0" } } diff --git a/packages/code-generator/CHANGELOG.md b/packages/code-generator/CHANGELOG.md new file mode 100644 index 000000000..e3d51cf5c --- /dev/null +++ b/packages/code-generator/CHANGELOG.md @@ -0,0 +1,52 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## [0.8.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-code-generator@0.8.3...@ali/lowcode-code-generator@0.8.4) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-code-generator + + +## [0.8.3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-code-generator@0.8.2...@ali/lowcode-code-generator@0.8.3) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-code-generator + + +## 0.8.2 (2020-03-30) + + +### Features + +* code generator main process ([021d6e0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/021d6e0)) +* demo schema & complex children type ([a5ee6bd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a5ee6bd)) +* fix gaps ([32af3d3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/32af3d3)) +* project builder fix & publish demo to disk ([26983b3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/26983b3)) + + + + + +## 0.8.1 (2020-03-30) + + +### Features + +<<<<<<< HEAD +* code generator main process ([021d6e0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/021d6e0)) +* demo schema & complex children type ([a5ee6bd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a5ee6bd)) +* fix gaps ([32af3d3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/32af3d3)) +* project builder fix & publish demo to disk ([26983b3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/26983b3)) +======= +* code generator main process ([021d6e0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/021d6e0fe9fb29a8b6c1c5d5f4d06ec71896faa5)) +* demo schema & complex children type ([a5ee6bd](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a5ee6bd55806fc9aea695096ccd4c7f50b8e31c4)) +* fix gaps ([32af3d3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/32af3d3a3ca4d5aca15be25e05c840c8ea0cb6ae)) +* project builder fix & publish demo to disk ([26983b3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/26983b38c2b0f1d39d79964eb54d8ce60250dd82)) +>>>>>>> df955e1db90ff104cd11160def80113cfd6faccc diff --git a/packages/code-generator/package.json b/packages/code-generator/package.json index 339562cb2..932ba54db 100644 --- a/packages/code-generator/package.json +++ b/packages/code-generator/package.json @@ -1,27 +1,34 @@ { - "name": "@ali/lowcode-engine-code-generator", - "version": "0.8.0", + "name": "@ali/lowcode-code-generator", + "version": "0.8.4", "description": "出码引擎 for LowCode Engine", "main": "lib/index.js", "files": [ "lib" ], "scripts": { - "build": "rimraf lib && tsc", + "compile": "rimraf lib && tsc", + "build": "rimraf lib && webpack", "demo": "ts-node -r tsconfig-paths/register ./src/demo/main.ts", "test": "ava" }, "dependencies": { "@ali/am-eslint-config": "*", + "@types/prettier": "^1.19.1", "change-case": "^3.1.0", + "prettier": "^2.0.2", "short-uuid": "^3.1.1" }, "devDependencies": { "ava": "^1.0.1", "rimraf": "^3.0.2", + "ts-loader": "^6.2.2", "ts-node": "^7.0.1", "tsconfig-paths": "^3.9.0", - "typescript": "^3.8.3" + "tsconfig-paths-webpack-plugin": "^3.2.0", + "webpack": "^4.42.1", + "webpack-cli": "^3.3.11", + "webpack-node-externals": "^1.7.2" }, "ava": { "compileEnhancements": false, diff --git a/packages/code-generator/src/generator/ModuleBuilder.ts b/packages/code-generator/src/generator/ModuleBuilder.ts index d52913d35..8115f0def 100644 --- a/packages/code-generator/src/generator/ModuleBuilder.ts +++ b/packages/code-generator/src/generator/ModuleBuilder.ts @@ -5,6 +5,7 @@ import { ICompiledModule, IModuleBuilder, IResultFile, + PostProcessor, } from '../types'; import { COMMON_SUB_MODULE_NAME } from '../const/generator'; @@ -17,9 +18,11 @@ import ResultFile from '../model/ResultFile'; export function createModuleBuilder( options: { plugins: BuilderComponentPlugin[]; + postProcessors: PostProcessor[]; mainFileName?: string; } = { plugins: [], + postProcessors: [], }, ): IModuleBuilder { const chunkGenerator = new ChunkBuilder(options.plugins); @@ -33,7 +36,7 @@ export function createModuleBuilder( ); } - const files: IResultFile[] = []; + let files: IResultFile[] = []; const { chunks } = await chunkGenerator.run(input); chunks.forEach(fileChunkList => { @@ -46,6 +49,18 @@ export function createModuleBuilder( files.push(file); }); + if (options.postProcessors.length > 0) { + files = files.map(file => { + let content = file.content; + const type = file.ext; + options.postProcessors.forEach(processer => { + content = processer(content, type); + }); + + return new ResultFile(file.name, type, content); + }); + } + return { files, }; diff --git a/packages/code-generator/src/generator/ProjectBuilder.ts b/packages/code-generator/src/generator/ProjectBuilder.ts index bc3513603..98f4dc0a4 100644 --- a/packages/code-generator/src/generator/ProjectBuilder.ts +++ b/packages/code-generator/src/generator/ProjectBuilder.ts @@ -8,10 +8,11 @@ import { IResultDir, IResultFile, ISchemaParser, + PostProcessor, } from '../types'; import ResultDir from '@/model/ResultDir'; -import SchemaParser from '@/parse/SchemaParser'; +import SchemaParser from '@/parser/SchemaParser'; import { createModuleBuilder } from '@/generator/ModuleBuilder'; @@ -40,16 +41,20 @@ function getDirFromRoot(root: IResultDir, path: string[]): IResultDir { export class ProjectBuilder implements IProjectBuilder { private template: IProjectTemplate; private plugins: IProjectPlugins; + private postProcessors: PostProcessor[]; constructor({ template, plugins, + postProcessors, }: { template: IProjectTemplate; plugins: IProjectPlugins; + postProcessors: PostProcessor[]; }) { this.template = template; this.plugins = plugins; + this.postProcessors = postProcessors; } public async generateProject(schema: IProjectSchema): Promise { @@ -212,45 +217,57 @@ export class ProjectBuilder implements IProjectBuilder { builders.components = createModuleBuilder({ plugins: this.plugins.components, + postProcessors: this.postProcessors, + }); + builders.pages = createModuleBuilder({ + plugins: this.plugins.pages, + postProcessors: this.postProcessors, }); - builders.pages = createModuleBuilder({ plugins: this.plugins.pages }); builders.router = createModuleBuilder({ plugins: this.plugins.router, mainFileName: this.template.slots.router.fileName, + postProcessors: this.postProcessors, }); builders.entry = createModuleBuilder({ plugins: this.plugins.entry, mainFileName: this.template.slots.entry.fileName, + postProcessors: this.postProcessors, }); builders.globalStyle = createModuleBuilder({ plugins: this.plugins.globalStyle, mainFileName: this.template.slots.globalStyle.fileName, + postProcessors: this.postProcessors, }); builders.htmlEntry = createModuleBuilder({ plugins: this.plugins.htmlEntry, mainFileName: this.template.slots.htmlEntry.fileName, + postProcessors: this.postProcessors, }); builders.packageJSON = createModuleBuilder({ plugins: this.plugins.packageJSON, mainFileName: this.template.slots.packageJSON.fileName, + postProcessors: this.postProcessors, }); if (this.template.slots.constants && this.plugins.constants) { builders.constants = createModuleBuilder({ plugins: this.plugins.constants, mainFileName: this.template.slots.constants.fileName, + postProcessors: this.postProcessors, }); } if (this.template.slots.utils && this.plugins.utils) { builders.utils = createModuleBuilder({ plugins: this.plugins.utils, mainFileName: this.template.slots.utils.fileName, + postProcessors: this.postProcessors, }); } if (this.template.slots.i18n && this.plugins.i18n) { builders.i18n = createModuleBuilder({ plugins: this.plugins.i18n, mainFileName: this.template.slots.i18n.fileName, + postProcessors: this.postProcessors, }); } @@ -261,12 +278,15 @@ export class ProjectBuilder implements IProjectBuilder { export function createProjectBuilder({ template, plugins, + postProcessors, }: { template: IProjectTemplate; plugins: IProjectPlugins; + postProcessors: PostProcessor[]; }): IProjectBuilder { return new ProjectBuilder({ template, plugins, + postProcessors, }); } diff --git a/packages/code-generator/src/index.ts b/packages/code-generator/src/index.ts index 12217cc69..f6d467bc7 100644 --- a/packages/code-generator/src/index.ts +++ b/packages/code-generator/src/index.ts @@ -3,6 +3,7 @@ * */ import { createProjectBuilder } from '@/generator/ProjectBuilder'; +import { createDiskPublisher } from '@/publisher/disk'; import createIceJsProjectBuilder from '@/solutions/icejs'; export * from './types'; @@ -12,4 +13,7 @@ export default { solutions: { icejs: createIceJsProjectBuilder, }, + publishers: { + disk: createDiskPublisher, + }, }; diff --git a/packages/code-generator/src/parse/SchemaParser.ts b/packages/code-generator/src/parser/SchemaParser.ts similarity index 100% rename from packages/code-generator/src/parse/SchemaParser.ts rename to packages/code-generator/src/parser/SchemaParser.ts diff --git a/packages/code-generator/src/postprocessor/index.ts b/packages/code-generator/src/postprocessor/index.ts new file mode 100644 index 000000000..5b122f637 --- /dev/null +++ b/packages/code-generator/src/postprocessor/index.ts @@ -0,0 +1,3 @@ +import prettier from './prettier'; + +export { prettier }; diff --git a/packages/code-generator/src/postprocessor/prettier/index.ts b/packages/code-generator/src/postprocessor/prettier/index.ts new file mode 100644 index 000000000..694038aa0 --- /dev/null +++ b/packages/code-generator/src/postprocessor/prettier/index.ts @@ -0,0 +1,22 @@ +import prettier from 'prettier'; + +import { PostProcessor } from '@/types'; + +const PARSERS = ['css', 'scss', 'less', 'json', 'html', 'vue']; + +const codePrettier: PostProcessor = (content: string, fileType: string) => { + let parser: prettier.BuiltInParserName; + if (fileType === 'js' || fileType === 'jsx') { + parser = 'babel'; + } else if (PARSERS.indexOf(fileType) >= 0) { + parser = fileType as prettier.BuiltInParserName; + } else { + return content; + } + + return prettier.format(content, { + parser, + }); +}; + +export default codePrettier; diff --git a/packages/code-generator/src/solutions/icejs.ts b/packages/code-generator/src/solutions/icejs.ts index d778fabac..e8091e69b 100644 --- a/packages/code-generator/src/solutions/icejs.ts +++ b/packages/code-generator/src/solutions/icejs.ts @@ -21,6 +21,8 @@ import template from '@/plugins/project/framework/icejs/template'; import i18n from '@/plugins/project/i18n'; import utils from '@/plugins/project/utils'; +import { prettier } from '@/postprocessor'; + export default function createIceJsProjectBuilder(): IProjectBuilder { return createProjectBuilder({ template, @@ -56,5 +58,6 @@ export default function createIceJsProjectBuilder(): IProjectBuilder { htmlEntry: [iceJsEntryHtml], packageJSON: [iceJsPackageJSON], }, + postProcessors: [prettier], }); } diff --git a/packages/code-generator/src/types/core.ts b/packages/code-generator/src/types/core.ts index fbe5ac151..8fedf317b 100644 --- a/packages/code-generator/src/types/core.ts +++ b/packages/code-generator/src/types/core.ts @@ -141,3 +141,5 @@ export interface IProjectPlugins { export interface IProjectBuilder { generateProject(schema: IProjectSchema): Promise; } + +export type PostProcessor = (content: string, fileType: string) => string; diff --git a/packages/code-generator/tsconfig.json b/packages/code-generator/tsconfig.json index 6c1c221e1..2660ef448 100644 --- a/packages/code-generator/tsconfig.json +++ b/packages/code-generator/tsconfig.json @@ -1,14 +1,17 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "target": "es6", - "module": "commonjs", - "outDir": "lib", + "target": "es5", + "strictNullChecks": true, + "inlineSources": false, + "lib": ["es6"], + "downlevelIteration": true, "paths": { "@/*": ["./src/*"] }, + "outDir": "./lib", "types": ["node"], - "baseUrl": ".", /* Base directory to resolve non-absolute module names. */ + "baseUrl": "." /* Base directory to resolve non-absolute module names. */ }, "include": [ "src/**/*" diff --git a/packages/code-generator/webpack.config.js b/packages/code-generator/webpack.config.js new file mode 100644 index 000000000..00f21d2e1 --- /dev/null +++ b/packages/code-generator/webpack.config.js @@ -0,0 +1,31 @@ +const path = require('path'); +const nodeExternals = require('webpack-node-externals'); +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); + +module.exports = { + mode: 'production', + target: 'node', + entry: { + index: './src/index.ts', + // demo: './src/demo/main.ts', + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: [ '.tsx', '.ts', '.js' ], + plugins: [new TsconfigPathsPlugin({/* options: see below */})], + }, + output: { + // filename: 'bundle.js', + filename: '[name].js', + path: path.resolve(__dirname, 'lib'), + }, + externals: [nodeExternals()], // in order to ignore all modules in node_modules folder +}; diff --git a/packages/demo/CHANGELOG.md b/packages/demo/CHANGELOG.md new file mode 100644 index 000000000..3895682a3 --- /dev/null +++ b/packages/demo/CHANGELOG.md @@ -0,0 +1,81 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## [0.8.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-demo@0.8.7...@ali/lowcode-demo@0.8.8) (2020-03-31) + + + + +**Note:** Version bump only for package @ali/lowcode-demo + + +## [0.8.7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-demo@0.8.6...@ali/lowcode-demo@0.8.7) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-demo + + +## [0.8.6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-demo@0.8.5...@ali/lowcode-demo@0.8.6) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-demo + + +## [0.8.5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-demo@0.8.4...@ali/lowcode-demo@0.8.5) (2020-03-30) + + +### Bug Fixes + +* depend ([c90996d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/c90996d)) + + + + + +## [0.8.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-demo@0.8.3...@ali/lowcode-demo@0.8.4) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-demo + + +## [0.8.3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-demo@0.8.2...@ali/lowcode-demo@0.8.3) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-demo + + +## 0.8.2 (2020-03-30) + + +### Features + +* complet preview ([56c16ff](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/56c16ff)) +* double outline & ZH_EN support ([b379bd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b379bd7)) + + + + + +## 0.8.1 (2020-03-30) + + +### Features + +<<<<<<< HEAD +* complet preview ([56c16ff](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/56c16ff)) +* double outline & ZH_EN support ([b379bd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b379bd7)) +======= +* complet preview ([56c16ff](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/56c16ffa5c39c2d01abd9cfa90fea49a4539da1d)) +* double outline & ZH_EN support ([b379bd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b379bd7c0c488ef24f825760750a13d3fa083c96)) +>>>>>>> df955e1db90ff104cd11160def80113cfd6faccc diff --git a/packages/demo/build.json b/packages/demo/build.json index 48cdb5e94..d09fc5a56 100644 --- a/packages/demo/build.json +++ b/packages/demo/build.json @@ -1,7 +1,8 @@ { "entry": { - "index": "src/index.jsx", - "react-simulator-renderer": "../react-simulator-renderer/src/index.js" + "index": "src/index.ts", + "react-simulator-renderer": "../react-simulator-renderer/src/index.ts", + "preview": "src/preview.ts" }, "vendor": false, "devServer": { @@ -15,7 +16,9 @@ "@alifd/next": "window.Next" }, "plugins": [ - ["build-plugin-react-app"], + [ + "build-plugin-react-app" + ], [ "build-plugin-fusion", { @@ -25,9 +28,11 @@ [ "build-plugin-moment-locales", { - "locales": ["zh-cn"] + "locales": [ + "zh-cn" + ] } ], "./build.plugin.js" ] -} +} \ No newline at end of file diff --git a/packages/demo/cloud-build.json b/packages/demo/cloud-build.json index 16a6afe1d..7b4fcd1d9 100644 --- a/packages/demo/cloud-build.json +++ b/packages/demo/cloud-build.json @@ -1,6 +1,7 @@ { "entry": { - "lowcode-demo": "src/index.jsx" + "lowcode-editor": "src/index.ts", + "lowcode-preview": "src/preview.ts" }, "vendor": false, "externals": { diff --git a/packages/demo/package.json b/packages/demo/package.json index 16ca0d3c8..bd8af2d92 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -1,28 +1,39 @@ { "name": "@ali/lowcode-demo", - "version": "0.8.0", + "version": "0.8.8", + "private": true, "description": "低代码引擎 DEMO", "scripts": { - "start": "build-scripts start", - "cloud-build": "build-scripts build --config cloud-build.json" + "cloud-build": "build-scripts build --config cloud-build.json", + "gen": "npm run genSkeleton && tyarn", + "genSkeleton": "iceluna gen lowcode -c ./skeleton.config.js -t ./src/editor/config", + "start": "build-scripts start" }, + "config": {}, "dependencies": { - "@ali/lowcode-editor-core": "^0.8", + "@ali/lowcode-editor-core": "^0.8.4", "@ali/lowcode-editor-skeleton": "^0.8.0", - "@ali/lowcode-plugin-designer": "^0.8.0", - "@ali/lowcode-setters": "^0.8.0", "@ali/lowcode-plugin-components-pane": "^0.8.0", - "@ali/lowcode-plugin-settings-pane": "^0.8.0", - "@ali/lowcode-plugin-outline-pane": "^0.8.0", - "@ali/lowcode-plugin-undo-redo": "^0.8.0", + "@ali/lowcode-plugin-designer": "^0.9.1", + "@ali/lowcode-plugin-event-bind-dialog": "^0.8.0", + "@ali/lowcode-plugin-outline-pane": "^0.8.7", "@ali/lowcode-plugin-sample-logo": "^0.8.0", - "@ali/lowcode-plugin-sample-preview": "^0.8.0", + "@ali/lowcode-plugin-sample-preview": "^0.8.6", + "@ali/lowcode-plugin-settings-pane": "^0.8.8", + "@ali/lowcode-plugin-undo-redo": "^0.8.0", + "@ali/lowcode-plugin-variable-bind-dialog": "^0.8.2", + "@ali/lowcode-plugin-zh-en": "^0.8.6", + "@ali/lowcode-react-renderer": "^0.8.4", + "@ali/lowcode-runtime": "^0.8.7", + "@ali/lowcode-setters": "^0.8.6", + "@alifd/next": "^1.19.12", "@alife/theme-lowcode-dark": "^0.1.0", "@alife/theme-lowcode-light": "^0.1.0", "react": "^16.8.1", "react-dom": "^16.8.1" }, "devDependencies": { + "@ali/iceluna-cli": "^0.0.16", "@alib/build-scripts": "^0.1.18", "@types/events": "^3.0.0", "@types/react": "^16.8.3", diff --git a/packages/demo/public/preview.html b/packages/demo/public/preview.html new file mode 100644 index 000000000..c8f2cfdcb --- /dev/null +++ b/packages/demo/public/preview.html @@ -0,0 +1,21 @@ + + + + + + + LowCodeEngine DEMO + + + + + + + + + + + +
+ + diff --git a/packages/demo/src/config/skeleton.js b/packages/demo/skeleton.config.js similarity index 73% rename from packages/demo/src/config/skeleton.js rename to packages/demo/skeleton.config.js index 035d9a0c5..4dd4cc1c0 100644 --- a/packages/demo/src/config/skeleton.js +++ b/packages/demo/skeleton.config.js @@ -1,5 +1,10 @@ -export default { - version: '^1.0.2', +module.exports = { + skeleton: { + config: { + package: '@ali/lowcode-editor-skeleton', + version: '^0.8.0' + } + }, theme: { fusion: { package: '@alife/theme-lowcode-light', @@ -22,7 +27,7 @@ export default { }, config: { package: '@ali/lowcode-plugin-sample-logo', - version: '1.0.0' + version: '^0.8.0' }, pluginProps: { logo: 'https://img.alicdn.com/tfs/TB1hoI9x1H2gK0jSZFEXXcqMpXa-146-40.png', @@ -38,7 +43,7 @@ export default { }, config: { package: '@ali/lowcode-plugin-undo-redo', - version: '1.0.0' + version: '^0.8.0' } }, { @@ -57,7 +62,7 @@ export default { }, config: { package: '@ali/lowcode-plugin-sample-preview', - version: '1.0.0' + version: '^0.8.0' } } ], @@ -68,14 +73,14 @@ export default { props: { align: 'top', icon: 'zujianku', - title: '组件库' + title: '组件库', + floatable: true, }, config: { - package: '@ali/iceluna-plugin-components-pane', - version: '0.0.1' + package: '@ali/lowcode-plugin-components-pane', + version: '^0.8.0' }, pluginProps: { - disableAppComponent: true } }, { @@ -83,12 +88,24 @@ export default { type: 'PanelIcon', props: { align: 'top', - icon: 'dengpao', + icon: 'shuxingkongjian', title: '大纲树' }, config: { package: '@ali/lowcode-plugin-outline-pane', - version: '^1.0.0' + version: '^0.8.0' + }, + pluginProps: {} + }, + { + pluginKey: 'zhEn', + type: 'Custom', + props: { + align: 'bottom', + }, + config: { + package: '@ali/lowcode-plugin-zh-en', + version: '^0.8.0' }, pluginProps: {} } @@ -100,7 +117,7 @@ export default { props: {}, config: { package: '@ali/lowcode-plugin-settings-pane', - version: '^1.0.0' + version: '^0.8.0' }, pluginProps: {} } @@ -110,7 +127,14 @@ export default { pluginKey: 'designer', config: { package: '@ali/lowcode-plugin-designer', - version: '1.0.0' + version: '^0.8.0' + } + }, + { + pluginKey: 'eventBindDialog', + config: { + package: '@ali/lowcode-plugin-event-bind-dialog', + version: '^0.8.0' } } ] diff --git a/packages/demo/src/app/config/app.ts b/packages/demo/src/app/config/app.ts new file mode 100644 index 000000000..6562cebbf --- /dev/null +++ b/packages/demo/src/app/config/app.ts @@ -0,0 +1,21 @@ +export default { + sdkVersion: '1.0.3', + history: 'hash', // 浏览器路由:brower 哈希路由:hash + containerId: 'lce-container', + layout: { + componentName: 'BasicLayout', + props: { + name: '低代码引擎预览 demo', + logo: { + src: 'https://img.alicdn.com/tfs/TB1L.1QAeL2gK0jSZFmXXc7iXXa-90-90.png', + width: 25, + height: 25, + }, + }, + }, + theme: { + package: '@alife/theme-fusion', + version: '^0.1.0', + }, + compDependencies: [], +}; diff --git a/packages/demo/src/app/config/components.ts b/packages/demo/src/app/config/components.ts new file mode 100644 index 000000000..3c4d25558 --- /dev/null +++ b/packages/demo/src/app/config/components.ts @@ -0,0 +1,5 @@ +/** + * 内置组件 + */ + +export default {}; diff --git a/packages/demo/src/app/config/componentsMap.ts b/packages/demo/src/app/config/componentsMap.ts new file mode 100644 index 000000000..a9bb73edb --- /dev/null +++ b/packages/demo/src/app/config/componentsMap.ts @@ -0,0 +1,53 @@ +export default { + Button: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Button', + }, + 'Button.Group': { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Button', + subName: 'Group', + }, + Input: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Input', + }, + Form: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Form', + }, + 'Form.Item': { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Form', + subName: 'Item', + }, + NumberPicker: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'NumberPicker', + }, + Select: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Select', + }, + 'Select.Option': { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Select', + subName: 'Option', + }, +}; diff --git a/packages/demo/src/config/utils.js b/packages/demo/src/app/config/constants.ts similarity index 100% rename from packages/demo/src/config/utils.js rename to packages/demo/src/app/config/constants.ts diff --git a/packages/demo/src/app/config/utils.ts b/packages/demo/src/app/config/utils.ts new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/packages/demo/src/app/config/utils.ts @@ -0,0 +1 @@ +export default {}; diff --git a/packages/demo/src/app/index.ts b/packages/demo/src/app/index.ts new file mode 100644 index 000000000..f4df24866 --- /dev/null +++ b/packages/demo/src/app/index.ts @@ -0,0 +1,20 @@ +import { app } from '@ali/lowcode-runtime'; +import Renderer from '@ali/lowcode-react-renderer'; +import FusionLoading from './plugins/loading/fusion'; +import BasicLayout from './layouts/BasicLayout'; +import Preview from './plugins/provider'; + +// 注册渲染模块 +app.registerRenderer(Renderer); + +// 注册布局组件,可注册多个 +app.registerLayout('BasicLayout', BasicLayout); + +// 注册页面 Loading +app.registerLoading(FusionLoading); + +// appKey:应用唯一标识 +app.registerProvider(Preview); + +// 启动应用 +app.run(); diff --git a/packages/demo/src/app/layouts/BasicLayout/index.scss b/packages/demo/src/app/layouts/BasicLayout/index.scss new file mode 100644 index 000000000..a8fe9848a --- /dev/null +++ b/packages/demo/src/app/layouts/BasicLayout/index.scss @@ -0,0 +1,24 @@ +$header-height: 52px; + +.avatar { + width: 24px; + height: 24px; + border-radius: 50%; + vertical-align: middle; +} + +.basic-shell { + min-height: 100vh; + .next-shell-header { + height: $header-height; + } + .next-shell-main { + flex: 1; + display: flex; + flex-flow: column; + min-height: calc(100% - $header-height); + .next-shell-sub-main { + flex: 1; + } + } +} diff --git a/packages/demo/src/app/layouts/BasicLayout/index.tsx b/packages/demo/src/app/layouts/BasicLayout/index.tsx new file mode 100644 index 000000000..3785e8339 --- /dev/null +++ b/packages/demo/src/app/layouts/BasicLayout/index.tsx @@ -0,0 +1,34 @@ +import { Search, Icon, Shell } from '@alifd/next'; +import './index.scss'; + +export default ({ + name, + children, + logo, +}: { + name: string; + children: any; + logo: { src: string; width: number; height: number }; +}) => ( + + + logo + {name} + + + + + + + 用户头像 + MyName + + + {children} + + + Alibaba Fusion + @ 2019 Alibaba Piecework 版权所有 + + +); diff --git a/packages/demo/src/app/plugins/loading/fusion/index.scss b/packages/demo/src/app/plugins/loading/fusion/index.scss new file mode 100644 index 000000000..0e53165cb --- /dev/null +++ b/packages/demo/src/app/plugins/loading/fusion/index.scss @@ -0,0 +1,9 @@ +.fusion-loading { + width: 48px; + height: 48px; + position: fixed; + top: 50%; + left: 50%; + margin-top: -24px; + margin-left: -24px; +} diff --git a/packages/demo/src/app/plugins/loading/fusion/index.tsx b/packages/demo/src/app/plugins/loading/fusion/index.tsx new file mode 100644 index 000000000..73de48418 --- /dev/null +++ b/packages/demo/src/app/plugins/loading/fusion/index.tsx @@ -0,0 +1,4 @@ +import { Loading } from '@alifd/next'; +import './index.scss'; + +export default () => ; diff --git a/packages/demo/src/app/plugins/provider.ts b/packages/demo/src/app/plugins/provider.ts new file mode 100644 index 000000000..3e5458bd7 --- /dev/null +++ b/packages/demo/src/app/plugins/provider.ts @@ -0,0 +1,51 @@ +import { ReactProvider, Utils } from '@ali/lowcode-runtime'; +import appConfig from '../config/app'; +import builtInComps from '../config/components'; +import componentsMap from '../config/componentsMap'; +import constants from '../config/constants'; +import utils from '../config/utils'; + +// 定制加载应用配置的逻辑 +export default class Preview extends ReactProvider { + // 定制获取、处理应用配置(组件、插件、路由模式、布局等)的逻辑 + async getAppData(): Promise { + const { history, layout, containerId } = appConfig; + const appSchemaStr: any = localStorage.getItem('lce-dev-store'); + if (!appSchemaStr) { + return; + } + const appSchema = JSON.parse(appSchemaStr); + if (!appSchema) { + return; + } + const routes: any = {}; + appSchema.componentsTree.forEach((page: any) => { + if (!page.fileName) { + return; + } + const pageId = page.fileName; + routes[pageId] = `/${pageId}`; + }); + return { + history, + layout, + routes, + containerId, + components: { ...builtInComps, ...Utils.buildComponents({ '@alifd/next': 'Next' }, componentsMap) }, + componentsMap, + utils: utils, + constants, + }; + } + + // 定制获取、处理页面 schema 的逻辑 + async getPageData(pageId: string) { + const appSchemaStr = localStorage.getItem('lce-dev-store'); + const appSchema = JSON.parse(appSchemaStr || ''); + const idx = appSchema.componentsTree.findIndex( + (page: any, idx: number) => (page.fileName || `page${idx}`) === pageId, + ); + const schema = appSchema.componentsTree[idx]; + return schema; + } +} diff --git a/packages/demo/src/config/components.js b/packages/demo/src/config/components.js deleted file mode 100644 index 2d40e553c..000000000 --- a/packages/demo/src/config/components.js +++ /dev/null @@ -1,18 +0,0 @@ -import undoRedo from '@ali/lowcode-plugin-undo-redo'; -import logo from '@ali/lowcode-plugin-sample-logo'; -import SamplePreview from '@ali/lowcode-plugin-sample-preview'; -import Designer from '@ali/lowcode-plugin-designer'; -import SettingsPane from '@ali/lowcode-plugin-settings-pane'; -import componentsPane from '@ali/lowcode-plugin-components-pane'; -import OutlinePane from '@ali/lowcode-plugin-outline-pane'; -import { PluginFactory } from '@ali/lowcode-editor-core'; - -export default { - logo: PluginFactory(logo), - samplePreview: PluginFactory(SamplePreview), - undoRedo: PluginFactory(undoRedo), - designer: PluginFactory(Designer), - componentsPane: PluginFactory(componentsPane), - settingsPane: PluginFactory(SettingsPane), - outlinePane: PluginFactory(OutlinePane), -}; diff --git a/packages/demo/src/config/constants.js b/packages/demo/src/config/constants.js deleted file mode 100644 index d7e16fed3..000000000 --- a/packages/demo/src/config/constants.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - namespace: 'page' -}; diff --git a/packages/demo/src/editor/config/components.js b/packages/demo/src/editor/config/components.js new file mode 100644 index 000000000..9648a7faf --- /dev/null +++ b/packages/demo/src/editor/config/components.js @@ -0,0 +1,22 @@ +import LowcodeSkeleton from '@ali/lowcode-editor-skeleton'; +import logo from '@ali/lowcode-plugin-sample-logo'; +import undoRedo from '@ali/lowcode-plugin-undo-redo'; +import samplePreview from '@ali/lowcode-plugin-sample-preview'; +import componentsPane from '@ali/lowcode-plugin-components-pane'; +import outlinePane from '@ali/lowcode-plugin-outline-pane'; +import zhEn from '@ali/lowcode-plugin-zh-en'; +import settingsPane from '@ali/lowcode-plugin-settings-pane'; +import designer from '@ali/lowcode-plugin-designer'; +import eventBindDialog from '@ali/lowcode-plugin-event-bind-dialog'; +export default { + LowcodeSkeleton, + logo, + undoRedo, + samplePreview, + componentsPane, + outlinePane, + zhEn, + settingsPane, + designer, + eventBindDialog +}; \ No newline at end of file diff --git a/packages/demo/src/editor/config/constants.js b/packages/demo/src/editor/config/constants.js new file mode 100644 index 000000000..1c713fc4e --- /dev/null +++ b/packages/demo/src/editor/config/constants.js @@ -0,0 +1,3 @@ +export default { + "namespace": "page" +} \ No newline at end of file diff --git a/packages/demo/src/editor/config/locale/en-US.js b/packages/demo/src/editor/config/locale/en-US.js new file mode 100644 index 000000000..7c645e42f --- /dev/null +++ b/packages/demo/src/editor/config/locale/en-US.js @@ -0,0 +1 @@ +export default {}; \ No newline at end of file diff --git a/packages/demo/src/editor/config/locale/index.js b/packages/demo/src/editor/config/locale/index.js new file mode 100644 index 000000000..eaa4da099 --- /dev/null +++ b/packages/demo/src/editor/config/locale/index.js @@ -0,0 +1,10 @@ +import en_us from './en-US'; +import zh_cn from './zh-CN'; +import zh_tw from './zh-TW'; +import ja_jp from './ja-JP'; +export default { + 'en-US': en_us, + 'zh-CN': zh_cn, + 'zh-TW': zh_tw, + 'ja-JP': ja_jp +}; \ No newline at end of file diff --git a/packages/demo/src/editor/config/locale/ja-JP.js b/packages/demo/src/editor/config/locale/ja-JP.js new file mode 100644 index 000000000..7c645e42f --- /dev/null +++ b/packages/demo/src/editor/config/locale/ja-JP.js @@ -0,0 +1 @@ +export default {}; \ No newline at end of file diff --git a/packages/demo/src/editor/config/locale/zh-CN.js b/packages/demo/src/editor/config/locale/zh-CN.js new file mode 100644 index 000000000..7c645e42f --- /dev/null +++ b/packages/demo/src/editor/config/locale/zh-CN.js @@ -0,0 +1 @@ +export default {}; \ No newline at end of file diff --git a/packages/demo/src/editor/config/locale/zh-TW.js b/packages/demo/src/editor/config/locale/zh-TW.js new file mode 100644 index 000000000..7c645e42f --- /dev/null +++ b/packages/demo/src/editor/config/locale/zh-TW.js @@ -0,0 +1 @@ +export default {}; \ No newline at end of file diff --git a/packages/demo/src/editor/config/skeleton.js b/packages/demo/src/editor/config/skeleton.js new file mode 100644 index 000000000..a772f94ee --- /dev/null +++ b/packages/demo/src/editor/config/skeleton.js @@ -0,0 +1,140 @@ +export default { + "skeleton": { + "config": { + "package": "@ali/lowcode-editor-skeleton", + "version": "^0.8.0" + } + }, + "theme": { + "fusion": { + "package": "@alife/theme-lowcode-light", + "version": "^0.1.0" + }, + "scss": "" + }, + "constants": { + "namespace": "page" + }, + "utils": [], + "plugins": { + "topArea": [{ + "pluginKey": "logo", + "type": "Custom", + "props": { + "align": "left", + "width": 100 + }, + "config": { + "package": "@ali/lowcode-plugin-sample-logo", + "version": "^0.8.0" + }, + "pluginProps": { + "logo": "https://img.alicdn.com/tfs/TB1hoI9x1H2gK0jSZFEXXcqMpXa-146-40.png", + "href": "/" + } + }, { + "pluginKey": "undoRedo", + "type": "Custom", + "props": { + "align": "right", + "width": 88 + }, + "config": { + "package": "@ali/lowcode-plugin-undo-redo", + "version": "^0.8.0" + } + }, { + "pluginKey": "divider", + "type": "Divider", + "props": { + "align": "right" + } + }, { + "pluginKey": "samplePreview", + "type": "Custom", + "props": { + "align": "right", + "width": 64 + }, + "config": { + "package": "@ali/lowcode-plugin-sample-preview", + "version": "^0.8.0" + } + }], + "leftArea": [{ + "pluginKey": "componentsPane", + "type": "PanelIcon", + "props": { + "align": "top", + "icon": "zujianku", + "title": "组件库", + "floatable": true + }, + "config": { + "package": "@ali/lowcode-plugin-components-pane", + "version": "^0.8.0" + }, + "pluginProps": {} + }, { + "pluginKey": "outlinePane", + "type": "PanelIcon", + "props": { + "align": "top", + "icon": "shuxingkongjian", + "title": "大纲树" + }, + "config": { + "package": "@ali/lowcode-plugin-outline-pane", + "version": "^0.8.0" + }, + "pluginProps": {} + }, { + "pluginKey": "zhEn", + "type": "Custom", + "props": { + "align": "bottom" + }, + "config": { + "package": "@ali/lowcode-plugin-zh-en", + "version": "^0.8.0" + }, + "pluginProps": {} + }], + "rightArea": [{ + "pluginKey": "settingsPane", + "type": "Panel", + "props": {}, + "config": { + "package": "@ali/lowcode-plugin-settings-pane", + "version": "^0.8.0" + }, + "pluginProps": {} + }], + "centerArea": [{ + "pluginKey": "designer", + "config": { + "package": "@ali/lowcode-plugin-designer", + "version": "^0.8.0" + } + }, { + "pluginKey": "eventBindDialog", + "config": { + "package": "@ali/lowcode-plugin-event-bind-dialog", + "version": "^0.8.0" + } + }] + }, + "hooks": [], + "shortCuts": [], + "lifeCycles": { + "init": async function init(editor) { + const assets = await editor.utils.get('./assets.json'); + editor.set('assets', assets); + editor.emit('assets.loaded', assets); + + const schema = await editor.utils.get('./schema.json'); + editor.set('schema', schema); + editor.emit('schema.loaded', schema); + } + } +}; diff --git a/packages/demo/src/config/theme.scss b/packages/demo/src/editor/config/theme.scss similarity index 100% rename from packages/demo/src/config/theme.scss rename to packages/demo/src/editor/config/theme.scss diff --git a/packages/demo/src/editor/config/utils.js b/packages/demo/src/editor/config/utils.js new file mode 100644 index 000000000..dd651ca49 --- /dev/null +++ b/packages/demo/src/editor/config/utils.js @@ -0,0 +1,3 @@ +export default { + +}; \ No newline at end of file diff --git a/packages/demo/src/global.scss b/packages/demo/src/editor/global.scss similarity index 100% rename from packages/demo/src/global.scss rename to packages/demo/src/editor/global.scss diff --git a/packages/demo/src/index.jsx b/packages/demo/src/editor/index.tsx similarity index 91% rename from packages/demo/src/index.jsx rename to packages/demo/src/editor/index.tsx index f666fe2c5..ab2d9526f 100644 --- a/packages/demo/src/index.jsx +++ b/packages/demo/src/editor/index.tsx @@ -1,6 +1,5 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import Skeleton from '@ali/lowcode-editor-skeleton'; import { registerSetters } from '@ali/lowcode-setters'; import config from './config/skeleton'; import components from './config/components'; @@ -10,7 +9,7 @@ import './global.scss'; import './config/theme.scss'; registerSetters(); - +const Skeleton = components.LowcodeSkeleton; const LCE_CONTAINER = document.getElementById('lce-container'); if (!LCE_CONTAINER) { diff --git a/packages/demo/src/index.ts b/packages/demo/src/index.ts new file mode 100644 index 000000000..eae3b122e --- /dev/null +++ b/packages/demo/src/index.ts @@ -0,0 +1 @@ +import './editor'; diff --git a/packages/demo/src/preview.ts b/packages/demo/src/preview.ts new file mode 100644 index 000000000..36c3352e9 --- /dev/null +++ b/packages/demo/src/preview.ts @@ -0,0 +1 @@ +import './app'; diff --git a/packages/demo/tsconfig.json b/packages/demo/tsconfig.json index 0c3363138..c37b76ecc 100644 --- a/packages/demo/tsconfig.json +++ b/packages/demo/tsconfig.json @@ -1,9 +1,9 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "jsx": "react", - "target": "ES6" + "outDir": "lib" }, - "include": ["src/*"], - "exclude": ["node_modules", "build", "public"] + "include": [ + "./src/" + ] } diff --git a/packages/designer/CHANGELOG.md b/packages/designer/CHANGELOG.md new file mode 100644 index 000000000..05d7b7ee3 --- /dev/null +++ b/packages/designer/CHANGELOG.md @@ -0,0 +1,74 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## [0.9.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-designer@0.9.0...@ali/lowcode-designer@0.9.1) (2020-03-31) + + + + +**Note:** Version bump only for package @ali/lowcode-designer + + +# [0.9.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-designer@0.8.5...@ali/lowcode-designer@0.9.0) (2020-03-30) + + +### Bug Fixes + +* **designer:** fix insertion style ([82c10d2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/82c10d2)) + + +### Features + +* **designer:** add blank page logic ([aeeb9ba](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/aeeb9ba)) +* **designer:** add builtin hotkeys ([2ec5883](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2ec5883)) + + + + + +## [0.8.5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-designer@0.8.4...@ali/lowcode-designer@0.8.5) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-designer + + +## [0.8.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-designer@0.8.3...@ali/lowcode-designer@0.8.4) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-designer + + +## 0.8.3 (2020-03-30) + + +### Features + +* 🎸 merge material-parser ([b40c286](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b40c286)) +* history log ([fbb3577](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fbb3577)) +* import react-docgen to parse propTypes ([6e66168](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6e66168)) + + + + + +## 0.8.2 (2020-03-30) + + +### Features + +<<<<<<< HEAD +* 🎸 merge material-parser ([b40c286](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b40c286)) +* history log ([fbb3577](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fbb3577)) +* import react-docgen to parse propTypes ([6e66168](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6e66168)) +======= +* 🎸 merge material-parser ([b40c286](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b40c2869a0bc901d855279735fe86b84dabaa04d)) +* history log ([fbb3577](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/fbb3577bd434c0ac77cc907abc36e3efe110fe8c)) +* import react-docgen to parse propTypes ([6e66168](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6e661686e4693e69279c496f3be1dd173703c55e)) +>>>>>>> df955e1db90ff104cd11160def80113cfd6faccc diff --git a/packages/designer/package.json b/packages/designer/package.json index ba93de0fd..6829f16c9 100644 --- a/packages/designer/package.json +++ b/packages/designer/package.json @@ -1,6 +1,6 @@ { "name": "@ali/lowcode-designer", - "version": "0.8.1", + "version": "0.9.1", "description": "Designer for Ali LowCode Engine", "main": "lib/index.js", "module": "es/index.js", @@ -15,18 +15,18 @@ }, "license": "MIT", "dependencies": { + "@ali/lowcode-globals": "^0.9.1", "classnames": "^2.2.6", "react": "^16", - "react-dom": "^16.7.0", - "@ali/lowcode-globals": "^0.8" + "react-dom": "^16.7.0" }, "devDependencies": { - "@types/medium-editor": "^5.0.3", + "@alib/build-scripts": "^0.1.18", "@types/classnames": "^2.2.7", + "@types/medium-editor": "^5.0.3", "@types/node": "^13.7.1", "@types/react": "^16", "@types/react-dom": "^16", - "@alib/build-scripts": "^0.1.18", "build-plugin-component": "^0.2.10" }, "ava": { diff --git a/packages/designer/src/builtin-simulator/bem-tools/insertion.less b/packages/designer/src/builtin-simulator/bem-tools/insertion.less index 8921e9334..770d3893c 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/insertion.less +++ b/packages/designer/src/builtin-simulator/bem-tools/insertion.less @@ -1,25 +1,27 @@ .lc-insertion { position: absolute; - top: -1.5px; + top: -2px; left: 0; z-index: 12; pointer-events: none !important; background-color: var(--color-brand-light); - height: 3px; + height: 4px; &.cover { top: 0; height: auto; width: auto; + border: none; opacity: 0.3; } &.vertical { top: 0; - left: -1.5px; - width: 3px; + left: -2px; + width: 4px; height: auto; } + &.invalid { background-color: red; } diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index b8fd42975..b47e2b3ec 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.ts @@ -20,11 +20,13 @@ import { getRectTarget, Rect, CanvasPoint, + hotkey, } from '../designer'; import { parseProps } from './utils/parse-props'; import { isElement } from '@ali/lowcode-globals'; import { ComponentMetadata } from '@ali/lowcode-globals'; import { BuiltinSimulatorRenderer } from './renderer'; +import clipboard from '../designer/clipboard'; export interface LibraryItem { package: string; @@ -196,6 +198,8 @@ export class BuiltinSimulatorHost implements ISimulatorHost x.ownerDocument === document)) { + return; + } const copyPaster = document.createElement<'textarea'>('textarea'); copyPaster.style.cssText = 'position: relative;left: -9999px;'; document.body.appendChild(copyPaster); diff --git a/packages/designer/src/designer/designer-view.tsx b/packages/designer/src/designer/designer-view.tsx index 7155188da..848f938c8 100644 --- a/packages/designer/src/designer/designer-view.tsx +++ b/packages/designer/src/designer/designer-view.tsx @@ -5,6 +5,7 @@ import BuiltinDragGhostComponent from './drag-ghost'; import { Designer, DesignerProps } from './designer'; import { ProjectView } from '../project'; import './designer.less'; +import clipboard from './clipboard'; export class DesignerView extends Component { readonly designer: Designer; @@ -32,6 +33,7 @@ export class DesignerView extends Component { if (onMount) { onMount(this.designer); } + clipboard.injectCopyPaster(document) this.designer.postEvent('mount', this.designer); } diff --git a/packages/designer/src/designer/designer.ts b/packages/designer/src/designer/designer.ts index c9eecb651..4312ea80e 100644 --- a/packages/designer/src/designer/designer.ts +++ b/packages/designer/src/designer/designer.ts @@ -10,7 +10,7 @@ import { autorun, } from '@ali/lowcode-globals'; import { Project } from '../project'; -import { Node, DocumentModel, insertChildren, isRootNode } from '../document'; +import { Node, DocumentModel, insertChildren, isRootNode, NodeParent } from '../document'; import { ComponentMeta } from '../component-meta'; import { INodeSelector } from '../simulator'; import { Scroller, IScrollable } from './scroller'; @@ -19,6 +19,7 @@ import { ActiveTracker } from './active-tracker'; import { Hovering } from './hovering'; import { DropLocation, LocationData, isLocationChildrenDetail } from './location'; import { OffsetObserver, createOffsetObserver } from './offset-observer'; +import { focusing } from './focusing'; export interface DesignerProps { className?: string; @@ -40,7 +41,6 @@ export interface DesignerProps { } export class Designer { - // readonly hotkey: Hotkey; readonly dragon = new Dragon(this); readonly activeTracker = new ActiveTracker(); readonly hovering = new Hovering(); @@ -156,6 +156,9 @@ export class Designer { this.postEvent('designer.init', this); setupSelection(); setupHistory(); + + // TODO: 先简单实现,后期通过焦点赋值 + focusing.focusDesigner = this; } postEvent(event: string, ...args: any[]) { @@ -198,7 +201,7 @@ export class Designer { /** * 获得合适的插入位置 */ - getSuitableInsertion() { + getSuitableInsertion(): { target: NodeParent; index?: number } | null { const activedDoc = this.project.currentDocument; if (!activedDoc) { return null; @@ -296,7 +299,7 @@ export class Designer { } setSchema(schema?: ProjectSchema) { - this.project.setSchema(schema); + this.project.load(schema); } @obx.val private _componentMetasMap = new Map(); diff --git a/packages/designer/src/designer/dragon.ts b/packages/designer/src/designer/dragon.ts index a66861756..e009a08a2 100644 --- a/packages/designer/src/designer/dragon.ts +++ b/packages/designer/src/designer/dragon.ts @@ -427,8 +427,9 @@ export class Dragon { }; const sourceSensor = getSourceSensor(dragObject); - const sensors: ISensor[] = (masterSensors as ISensor[]).concat(this.sensors); const chooseSensor = (e: LocateEvent) => { + // this.sensors will change on dragstart + const sensors: ISensor[] = (masterSensors as ISensor[]).concat(this.sensors); let sensor = e.sensor && e.sensor.isEnter(e) ? e.sensor : sensors.find((s) => s.sensorAvailable && s.isEnter(e)); if (!sensor) { // TODO: enter some area like componentspanel cancel diff --git a/packages/designer/src/designer/focusing.ts b/packages/designer/src/designer/focusing.ts new file mode 100644 index 000000000..06a1495f3 --- /dev/null +++ b/packages/designer/src/designer/focusing.ts @@ -0,0 +1,9 @@ +import { Designer } from './designer'; + +// TODO: +// 当前激活区域管理 +class Focusing { + focusDesigner?: Designer; +} + +export const focusing = new Focusing(); diff --git a/packages/designer/src/designer/hotkey.ts b/packages/designer/src/designer/hotkey.ts index 42a589025..a77c64a9e 100644 --- a/packages/designer/src/designer/hotkey.ts +++ b/packages/designer/src/designer/hotkey.ts @@ -1,618 +1,117 @@ -interface KeyMap { - [key: number]: string; -} +import { Hotkey, isFormEvent } from '@ali/lowcode-globals'; +import { focusing } from './focusing'; +import { insertChildren } from '../document'; +import clipboard from './clipboard'; -interface CtrlKeyMap { - [key: string]: string; -} +export const hotkey = new Hotkey(); -interface ActionEvent { - type: string; -} - -interface HotkeyCallbacks { - [key: string]: HotkeyCallbackCfg[]; -} - -interface HotkeyDirectMap { - [key: string]: HotkeyCallback; -} - -export type HotkeyCallback = (e: KeyboardEvent, combo?: string) => any | false; - -interface HotkeyCallbackCfg { - callback: HotkeyCallback; - modifiers: string[]; - action: string; - seq?: string; - level?: number; - combo?: string; -} - -interface KeyInfo { - key: string; - modifiers: string[]; - action: string; -} - -interface SequenceLevels { - [key: string]: number; -} - -const MAP: KeyMap = { - 8: 'backspace', - 9: 'tab', - 13: 'enter', - 16: 'shift', - 17: 'ctrl', - 18: 'alt', - 20: 'capslock', - 27: 'esc', - 32: 'space', - 33: 'pageup', - 34: 'pagedown', - 35: 'end', - 36: 'home', - 37: 'left', - 38: 'up', - 39: 'right', - 40: 'down', - 45: 'ins', - 46: 'del', - 91: 'meta', - 93: 'meta', - 224: 'meta', -}; - -const KEYCODE_MAP: KeyMap = { - 106: '*', - 107: '+', - 109: '-', - 110: '.', - 111: '/', - 186: ';', - 187: '=', - 188: ',', - 189: '-', - 190: '.', - 191: '/', - 192: '`', - 219: '[', - 220: '\\', - 221: ']', - 222: "'", -}; - -const SHIFT_MAP: CtrlKeyMap = { - '~': '`', - '!': '1', - '@': '2', - '#': '3', - $: '4', - '%': '5', - '^': '6', - '&': '7', - '*': '8', - '(': '9', - ')': '0', - _: '-', - '+': '=', - ':': ';', - '"': "'", - '<': ',', - '>': '.', - '?': '/', - '|': '\\', -}; - -const SPECIAL_ALIASES: CtrlKeyMap = { - option: 'alt', - command: 'meta', - return: 'enter', - escape: 'esc', - plus: '+', - mod: /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl', -}; - -let REVERSE_MAP: CtrlKeyMap; - -/** - * loop through the f keys, f1 to f19 and add them to the map - * programatically - */ -for (let i = 1; i < 20; ++i) { - MAP[111 + i] = 'f' + i; -} - -/** - * loop through to map numbers on the numeric keypad - */ -for (let i = 0; i <= 9; ++i) { - MAP[i + 96] = String(i); -} - -/** - * takes the event and returns the key character - */ -function characterFromEvent(e: KeyboardEvent): string { - const keyCode = e.keyCode || e.which; - // for keypress events we should return the character as is - if (e.type === 'keypress') { - let character = String.fromCharCode(keyCode); - // if the shift key is not pressed then it is safe to assume - // that we want the character to be lowercase. this means if - // you accidentally have caps lock on then your key bindings - // will continue to work - // - // the only side effect that might not be desired is if you - // bind something like 'A' cause you want to trigger an - // event when capital A is pressed caps lock will no longer - // trigger the event. shift+a will though. - if (!e.shiftKey) { - character = character.toLowerCase(); - } - return character; +// hotkey binding +hotkey.bind(['backspace', 'del'], (e: KeyboardEvent) => { + const doc = focusing.focusDesigner?.currentDocument; + if (isFormEvent(e) || !doc) { + return; } - // for non keypress events the special maps are needed - if (MAP[keyCode]) { - return MAP[keyCode]; + e.preventDefault(); + + const sel = doc.selection; + const topItems = sel.getTopNodes(); + // TODO: check can remove + topItems.forEach(node => { + doc.removeNode(node); + }); + sel.clear(); +}); + +hotkey.bind('escape', (e: KeyboardEvent) => { + // const currentFocus = focusing.current; + const sel = focusing.focusDesigner?.currentDocument?.selection; + if (isFormEvent(e) || !sel) { + return; } - if (KEYCODE_MAP[keyCode]) { - return KEYCODE_MAP[keyCode]; + e.preventDefault(); + + sel.clear(); + // currentFocus.esc(); +}); + +// command + c copy command + x cut +hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => { + const doc = focusing.focusDesigner?.currentDocument; + if (isFormEvent(e) || !doc) { + return; } - // if it is not in the special map - // with keydown and keyup events the character seems to always - // come in as an uppercase character whether you are pressing shift - // or not. we should make sure it is always lowercase for comparisons - return String.fromCharCode(keyCode).toLowerCase(); -} + e.preventDefault(); -interface KeypressEvent extends KeyboardEvent { - type: 'keypress'; -} - -function isPressEvent(e: KeyboardEvent | ActionEvent): e is KeypressEvent { - return e.type === 'keypress'; -} - -export function isFormEvent(e: KeyboardEvent) { - const t = e.target as HTMLFormElement; - if (!t) { - return false; + /* + const doc = getCurrentDocument(); + if (isFormEvent(e) || !doc || !(focusing.id === 'outline' || focusing.id === 'canvas')) { + return; } + e.preventDefault(); + */ - if (t.form || /^(INPUT|SELECT|TEXTAREA)$/.test(t.tagName)) { - return true; + const selected = doc.selection.getTopNodes(true); + if (!selected || selected.length < 1) return; + + const componentsMap = {}; + const componentsTree = selected.map(item => item.export(false)); + + const data = { type: 'nodeSchema', componentsMap, componentsTree }; + + clipboard.setData(data); + /* + const cutMode = action.indexOf('x') > 0; + if (cutMode) { + const parentNode = selected.getParent(); + parentNode.select(); + selected.remove(); } - if (/write/.test(window.getComputedStyle(t).getPropertyValue('-webkit-user-modify'))) { - return true; + */ +}); + +// command + v paste +hotkey.bind(['command+v', 'ctrl+v'], (e) => { + const designer = focusing.focusDesigner; + const doc = designer?.currentDocument; + if (isFormEvent(e) || !designer || !doc) { + return; } - return false; -} -/** - * checks if two arrays are equal - */ -function modifiersMatch(modifiers1: string[], modifiers2: string[]): boolean { - return modifiers1.sort().join(',') === modifiers2.sort().join(','); -} - -/** - * takes a key event and figures out what the modifiers are - */ -function eventModifiers(e: KeyboardEvent): string[] { - const modifiers = []; - - if (e.shiftKey) { - modifiers.push('shift'); - } - - if (e.altKey) { - modifiers.push('alt'); - } - - if (e.ctrlKey) { - modifiers.push('ctrl'); - } - - if (e.metaKey) { - modifiers.push('meta'); - } - - return modifiers; -} - -/** - * determines if the keycode specified is a modifier key or not - */ -function isModifier(key: string): boolean { - return key === 'shift' || key === 'ctrl' || key === 'alt' || key === 'meta'; -} - -/** - * reverses the map lookup so that we can look for specific keys - * to see what can and can't use keypress - * - * @return {Object} - */ -function getReverseMap(): CtrlKeyMap { - if (!REVERSE_MAP) { - REVERSE_MAP = {}; - for (const key in MAP) { - // pull out the numeric keypad from here cause keypress should - // be able to detect the keys from the character - if (Number(key) > 95 && Number(key) < 112) { - continue; + clipboard.waitPasteData(e, ({ componentsTree }) => { + if (componentsTree) { + const { target, index } = designer.getSuitableInsertion() || {}; + if (!target) { + return; } - - if (MAP.hasOwnProperty(key)) { - REVERSE_MAP[MAP[key]] = key; + const nodes = insertChildren(target, componentsTree, index); + if (nodes) { + doc.selection.selectAll(nodes.map(o => o.id)); + setTimeout(() => designer.activeTracker.track(nodes[0]), 10); } } - } - return REVERSE_MAP; -} + }); +}); -/** - * picks the best action based on the key combination - */ -function pickBestAction(key: string, modifiers: string[], action?: string): string { - // if no action was picked in we should try to pick the one - // that we think would work best for this key - if (!action) { - action = getReverseMap()[key] ? 'keydown' : 'keypress'; - } - // modifier keys don't work as expected with keypress, - // switch to keydown - if (action === 'keypress' && modifiers.length) { - action = 'keydown'; - } - return action; -} -/** - * Converts from a string key combination to an array - * - * @param {string} combination like "command+shift+l" - * @return {Array} - */ -function keysFromString(combination: string): string[] { - if (combination === '+') { - return ['+']; +// command + z undo +hotkey.bind(['command+z', 'ctrl+z'], (e) => { + const his = focusing.focusDesigner?.currentHistory; + if (isFormEvent(e) || !his) { + return; } - combination = combination.replace(/\+{2}/g, '+plus'); - return combination.split('+'); -} + e.preventDefault(); + his.back(); +}); -/** - * Gets info for a specific key combination - * - * @param combination key combination ("command+s" or "a" or "*") - */ -function getKeyInfo(combination: string, action?: string): KeyInfo { - let keys: string[] = []; - let key = ''; - let i: number; - const modifiers: string[] = []; - - // take the keys from this pattern and figure out what the actual - // pattern is all about - keys = keysFromString(combination); - - for (i = 0; i < keys.length; ++i) { - key = keys[i]; - - // normalize key names - if (SPECIAL_ALIASES[key]) { - key = SPECIAL_ALIASES[key]; - } - - // if this is not a keypress event then we should - // be smart about using shift keys - // this will only work for US keyboards however - if (action && action !== 'keypress' && SHIFT_MAP[key]) { - key = SHIFT_MAP[key]; - modifiers.push('shift'); - } - - // if this key is a modifier then add it to the list of modifiers - if (isModifier(key)) { - modifiers.push(key); - } +// command + shift + z redo +hotkey.bind(['command+y', 'ctrl+y', 'command+shift+z'], (e) => { + const his = focusing.focusDesigner?.currentHistory; + if (isFormEvent(e) || !his) { + return; } + e.preventDefault(); - // depending on what the key combination is - // we will try to pick the best event for it - action = pickBestAction(key, modifiers, action); + his.forward(); +}); - return { - key, - modifiers, - action, - }; -} - -/** - * actually calls the callback function - * - * if your callback function returns false this will use the jquery - * convention - prevent default and stop propogation on the event - */ -function fireCallback(callback: HotkeyCallback, e: KeyboardEvent, combo?: string, sequence?: string): void { - if (callback(e, combo) === false) { - e.preventDefault(); - e.stopPropagation(); - } -} - -export class Hotkey { - private callBacks: HotkeyCallbacks = {}; - private directMap: HotkeyDirectMap = {}; - private sequenceLevels: SequenceLevels = {}; - private resetTimer = 0; - private ignoreNextKeyup: boolean | string = false; - private ignoreNextKeypress = false; - private nextExpectedAction: boolean | string = false; - - mount(window: Window) { - const document = window.document; - const handleKeyEvent = this.handleKeyEvent.bind(this); - document.addEventListener('keypress', handleKeyEvent, false); - document.addEventListener('keydown', handleKeyEvent, false); - document.addEventListener('keyup', handleKeyEvent, false); - return () => { - document.removeEventListener('keypress', handleKeyEvent, false); - document.removeEventListener('keydown', handleKeyEvent, false); - document.removeEventListener('keyup', handleKeyEvent, false); - }; - } - - bind(combos: string[] | string, callback: HotkeyCallback, action?: string): Hotkey { - this.bindMultiple(Array.isArray(combos) ? combos : [combos], callback, action); - return this; - } - - /** - * resets all sequence counters except for the ones passed in - */ - private resetSequences(doNotReset?: SequenceLevels): void { - // doNotReset = doNotReset || {}; - let activeSequences = false; - let key = ''; - for (key in this.sequenceLevels) { - if (doNotReset && doNotReset[key]) { - activeSequences = true; - } else { - this.sequenceLevels[key] = 0; - } - } - if (!activeSequences) { - this.nextExpectedAction = false; - } - } - - /** - * finds all callbacks that match based on the keycode, modifiers, - * and action - */ - private getMatches( - character: string, - modifiers: string[], - e: KeyboardEvent | ActionEvent, - sequenceName?: string, - combination?: string, - level?: number, - ): HotkeyCallbackCfg[] { - let i: number; - let callback: HotkeyCallbackCfg; - const matches: HotkeyCallbackCfg[] = []; - const action: string = e.type; - - // if there are no events related to this keycode - if (!this.callBacks[character]) { - return []; - } - - // if a modifier key is coming up on its own we should allow it - if (action === 'keyup' && isModifier(character)) { - modifiers = [character]; - } - - // loop through all callbacks for the key that was pressed - // and see if any of them match - for (i = 0; i < this.callBacks[character].length; ++i) { - callback = this.callBacks[character][i]; - - // if a sequence name is not specified, but this is a sequence at - // the wrong level then move onto the next match - if (!sequenceName && callback.seq && this.sequenceLevels[callback.seq] !== callback.level) { - continue; - } - - // if the action we are looking for doesn't match the action we got - // then we should keep going - if (action !== callback.action) { - continue; - } - - // if this is a keypress event and the meta key and control key - // are not pressed that means that we need to only look at the - // character, otherwise check the modifiers as well - // - // chrome will not fire a keypress if meta or control is down - // safari will fire a keypress if meta or meta+shift is down - // firefox will fire a keypress if meta or control is down - if ((isPressEvent(e) && !e.metaKey && !e.ctrlKey) || modifiersMatch(modifiers, callback.modifiers)) { - const deleteCombo = !sequenceName && callback.combo === combination; - const deleteSequence = sequenceName && callback.seq === sequenceName && callback.level === level; - if (deleteCombo || deleteSequence) { - this.callBacks[character].splice(i, 1); - } - - matches.push(callback); - } - } - return matches; - } - - private handleKey(character: string, modifiers: string[], e: KeyboardEvent): void { - const callbacks: HotkeyCallbackCfg[] = this.getMatches(character, modifiers, e); - let i: number; - const doNotReset: SequenceLevels = {}; - let maxLevel = 0; - let processedSequenceCallback = false; - - // Calculate the maxLevel for sequences so we can only execute the longest callback sequence - for (i = 0; i < callbacks.length; ++i) { - if (callbacks[i].seq) { - maxLevel = Math.max(maxLevel, callbacks[i].level || 0); - } - } - - // loop through matching callbacks for this key event - for (i = 0; i < callbacks.length; ++i) { - // fire for all sequence callbacks - // this is because if for example you have multiple sequences - // bound such as "g i" and "g t" they both need to fire the - // callback for matching g cause otherwise you can only ever - // match the first one - if (callbacks[i].seq) { - // only fire callbacks for the maxLevel to prevent - // subsequences from also firing - // - // for example 'a option b' should not cause 'option b' to fire - // even though 'option b' is part of the other sequence - // - // any sequences that do not match here will be discarded - // below by the resetSequences call - if (callbacks[i].level !== maxLevel) { - continue; - } - - processedSequenceCallback = true; - - // keep a list of which sequences were matches for later - doNotReset[callbacks[i].seq || ''] = 1; - fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq); - continue; - } - - // if there were no sequence matches but we are still here - // that means this is a regular match so we should fire that - if (!processedSequenceCallback) { - fireCallback(callbacks[i].callback, e, callbacks[i].combo); - } - } - - const ignoreThisKeypress = e.type === 'keypress' && this.ignoreNextKeypress; - if (e.type === this.nextExpectedAction && !isModifier(character) && !ignoreThisKeypress) { - this.resetSequences(doNotReset); - } - - this.ignoreNextKeypress = processedSequenceCallback && e.type === 'keydown'; - } - - private handleKeyEvent(e: KeyboardEvent): void { - const character = characterFromEvent(e); - - // no character found then stop - if (!character) { - return; - } - - // need to use === for the character check because the character can be 0 - if (e.type === 'keyup' && this.ignoreNextKeyup === character) { - this.ignoreNextKeyup = false; - return; - } - - this.handleKey(character, eventModifiers(e), e); - } - - private resetSequenceTimer(): void { - if (this.resetTimer) { - clearTimeout(this.resetTimer); - } - this.resetTimer = window.setTimeout(this.resetSequences, 1000); - } - - private bindSequence(combo: string, keys: string[], callback: HotkeyCallback, action?: string): void { - // const self: any = this; - this.sequenceLevels[combo] = 0; - const increaseSequence = (nextAction: string) => { - return () => { - this.nextExpectedAction = nextAction; - ++this.sequenceLevels[combo]; - this.resetSequenceTimer(); - }; - }; - const callbackAndReset = (e: KeyboardEvent): void => { - fireCallback(callback, e, combo); - - if (action !== 'keyup') { - this.ignoreNextKeyup = characterFromEvent(e); - } - - setTimeout(this.resetSequences, 10); - }; - for (let i = 0; i < keys.length; ++i) { - const isFinal = i + 1 === keys.length; - const wrappedCallback = isFinal ? callbackAndReset : increaseSequence(action || getKeyInfo(keys[i + 1]).action); - this.bindSingle(keys[i], wrappedCallback, action, combo, i); - } - } - - private bindSingle( - combination: string, - callback: HotkeyCallback, - action?: string, - sequenceName?: string, - level?: number, - ): void { - // store a direct mapped reference for use with HotKey.trigger - this.directMap[`${combination}:${action}`] = callback; - - // make sure multiple spaces in a row become a single space - combination = combination.replace(/\s+/g, ' '); - - const sequence: string[] = combination.split(' '); - let info: KeyInfo; - - // if this pattern is a sequence of keys then run through this method - // to reprocess each pattern one key at a time - if (sequence.length > 1) { - this.bindSequence(combination, sequence, callback, action); - return; - } - - info = getKeyInfo(combination, action); - - // make sure to initialize array if this is the first time - // a callback is added for this key - this.callBacks[info.key] = this.callBacks[info.key] || []; - - // remove an existing match if there is one - this.getMatches(info.key, info.modifiers, { type: info.action }, sequenceName, combination, level); - - // add this call back to the array - // if it is a sequence put it at the beginning - // if not put it at the end - // - // this is important because the way these are processed expects - // the sequence ones to come first - this.callBacks[info.key][sequenceName ? 'unshift' : 'push']({ - callback, - modifiers: info.modifiers, - action: info.action, - seq: sequenceName, - level, - combo: combination, - }); - } - - private bindMultiple(combinations: string[], callback: HotkeyCallback, action?: string) { - for (const item of combinations) { - this.bindSingle(item, callback, action); - } - } -} +hotkey.mount(window); diff --git a/packages/designer/src/designer/location.ts b/packages/designer/src/designer/location.ts index eef13d10d..2c82891ca 100644 --- a/packages/designer/src/designer/location.ts +++ b/packages/designer/src/designer/location.ts @@ -86,7 +86,7 @@ export function isChildInline(child: Element | Text, win?: Window) { return true; } const style = (win || getWindow(child)).getComputedStyle(child); - return /^inline/.test(style.getPropertyValue('display')); + return /^inline/.test(style.getPropertyValue('display')) || /^(left|right)$/.test(style.getPropertyValue('float')); } export function getRectTarget(rect: Rect | null) { diff --git a/packages/designer/src/document/document-model.ts b/packages/designer/src/document/document-model.ts index 391816d0d..8676972d0 100644 --- a/packages/designer/src/document/document-model.ts +++ b/packages/designer/src/document/document-model.ts @@ -59,6 +59,7 @@ export class DocumentModel { } private _modalNode?: NodeParent; + private _blank?: boolean; get modalNode() { return this._modalNode; } @@ -67,7 +68,7 @@ export class DocumentModel { return this.modalNode || this.rootNode; } - constructor(readonly project: Project, schema: RootSchema) { + constructor(readonly project: Project, schema?: RootSchema) { autorun(() => { this.nodes.forEach((item) => { if (item.parent == null && item !== this.rootNode) { @@ -75,7 +76,16 @@ export class DocumentModel { } }); }, true); - this.rootNode = this.createRootNode(schema); + + if (!schema) { + this._blank = true; + } + + this.rootNode = this.createRootNode(schema || { + componentName: 'Page', + fileName: '' + }); + this.history = new History( () => this.schema, (schema) => this.import(schema as RootSchema, true), @@ -83,6 +93,10 @@ export class DocumentModel { this.setupListenActiveNodes(); } + @computed isBlank() { + return this._blank && !this.isModified(); + } + readonly designer = this.project.designer; /** @@ -295,6 +309,7 @@ export class DocumentModel { } private mountSimulator(simulator: ISimulatorHost) { + // TODO: 多设备 simulator 支持 this._simulator = simulator; // TODO: emit simulator mounted } @@ -386,7 +401,8 @@ export class DocumentModel { * 从项目中移除 */ remove() { - // todo: + // this.project.removeDocument(this); + // todo: ... } purge() { diff --git a/packages/globals/src/icons/clone.tsx b/packages/designer/src/icons/clone.tsx similarity index 92% rename from packages/globals/src/icons/clone.tsx rename to packages/designer/src/icons/clone.tsx index 12b65a9c9..374f061df 100644 --- a/packages/globals/src/icons/clone.tsx +++ b/packages/designer/src/icons/clone.tsx @@ -1,10 +1,10 @@ -import { IconBase, IconProps } from "./icon-base"; +import { SVGIcon, IconProps } from "@ali/lowcode-globals"; export function IconClone(props: IconProps) { return ( - + - + ); } diff --git a/packages/globals/src/icons/component.tsx b/packages/designer/src/icons/component.tsx similarity index 92% rename from packages/globals/src/icons/component.tsx rename to packages/designer/src/icons/component.tsx index a612a5be3..26b99e902 100644 --- a/packages/globals/src/icons/component.tsx +++ b/packages/designer/src/icons/component.tsx @@ -1,10 +1,10 @@ -import { IconBase, IconProps } from "./icon-base"; +import { SVGIcon, IconProps } from "@ali/lowcode-globals"; export function IconComponent(props: IconProps) { return ( - + - + ); } IconComponent.displayName = 'Component'; diff --git a/packages/globals/src/icons/container.tsx b/packages/designer/src/icons/container.tsx similarity index 86% rename from packages/globals/src/icons/container.tsx rename to packages/designer/src/icons/container.tsx index c6a928ec5..62a8cffba 100644 --- a/packages/globals/src/icons/container.tsx +++ b/packages/designer/src/icons/container.tsx @@ -1,11 +1,11 @@ -import { IconBase, IconProps } from "./icon-base"; +import { SVGIcon, IconProps } from "@ali/lowcode-globals"; export function IconContainer(props: IconProps) { return ( - + - + ); } IconContainer.displayName = 'Container'; diff --git a/packages/globals/src/icons/hidden.tsx b/packages/designer/src/icons/hidden.tsx similarity index 93% rename from packages/globals/src/icons/hidden.tsx rename to packages/designer/src/icons/hidden.tsx index 85c38d177..e3a001ca4 100644 --- a/packages/globals/src/icons/hidden.tsx +++ b/packages/designer/src/icons/hidden.tsx @@ -1,10 +1,10 @@ -import { IconBase, IconProps } from "./icon-base"; +import { SVGIcon, IconProps } from "@ali/lowcode-globals"; export function IconHidden(props: IconProps) { return ( - + - + ); } IconHidden.displayName = 'Hidden'; diff --git a/packages/globals/src/icons/page.tsx b/packages/designer/src/icons/page.tsx similarity index 92% rename from packages/globals/src/icons/page.tsx rename to packages/designer/src/icons/page.tsx index 8f5a770f6..1762b3d94 100644 --- a/packages/globals/src/icons/page.tsx +++ b/packages/designer/src/icons/page.tsx @@ -1,12 +1,12 @@ -import { IconBase, IconProps } from "./icon-base"; +import { SVGIcon, IconProps } from "@ali/lowcode-globals"; export function IconPage(props: IconProps) { return ( - + - + ); } IconPage.displayName = 'Page'; diff --git a/packages/globals/src/icons/remove.tsx b/packages/designer/src/icons/remove.tsx similarity index 88% rename from packages/globals/src/icons/remove.tsx rename to packages/designer/src/icons/remove.tsx index 4e454ac65..e10bd8e04 100644 --- a/packages/globals/src/icons/remove.tsx +++ b/packages/designer/src/icons/remove.tsx @@ -1,10 +1,10 @@ -import { IconBase, IconProps } from './icon-base'; +import { SVGIcon, IconProps } from '@ali/lowcode-globals'; export function IconRemove(props: IconProps) { return ( - + - + ); } IconRemove.displayName = 'Remove'; diff --git a/packages/globals/src/icons/setting.tsx b/packages/designer/src/icons/setting.tsx similarity index 94% rename from packages/globals/src/icons/setting.tsx rename to packages/designer/src/icons/setting.tsx index 8b1da4410..d928bda86 100644 --- a/packages/globals/src/icons/setting.tsx +++ b/packages/designer/src/icons/setting.tsx @@ -1,11 +1,11 @@ -import { IconBase, IconProps } from './icon-base'; +import { SVGIcon, IconProps } from '@ali/lowcode-globals'; export function IconSetting(props: IconProps) { return ( - + - + ); } diff --git a/packages/designer/src/locale/en-US.json b/packages/designer/src/locale/en-US.json index 9f5c5f234..69eb61330 100644 --- a/packages/designer/src/locale/en-US.json +++ b/packages/designer/src/locale/en-US.json @@ -1,5 +1,6 @@ { "copy": "Copy", "remove": "Remove", - "Condition Group": "Condition Group" + "Condition Group": "Condition Group", + "No opened document": "No opened document, open some document to editing" } diff --git a/packages/designer/src/locale/zh-CN.json b/packages/designer/src/locale/zh-CN.json index 39042c3af..cafba4f51 100644 --- a/packages/designer/src/locale/zh-CN.json +++ b/packages/designer/src/locale/zh-CN.json @@ -1,5 +1,6 @@ { "copy": "复制", "remove": "删除", - "Condition Group": "条件组" + "Condition Group": "条件组", + "No opened document": "没有打开的页面,请选择页面打开编辑" } diff --git a/packages/designer/src/project/project-view.tsx b/packages/designer/src/project/project-view.tsx index e1d23a00b..cfec45792 100644 --- a/packages/designer/src/project/project-view.tsx +++ b/packages/designer/src/project/project-view.tsx @@ -2,6 +2,7 @@ import { Component } from 'react'; import { observer } from '@ali/lowcode-globals'; import { Designer } from '../designer'; import { DocumentView } from '../document'; +import { intl } from '../locale'; import './project.less'; @observer @@ -9,14 +10,14 @@ export class ProjectView extends Component<{ designer: Designer }> { render() { const { designer } = this.props; // TODO: support splitview + const opens = designer.project.documents.filter((doc) => doc.opened); return (
- {designer.project.documents.map(doc => { - if (!doc.opened) { - return null; - } - return ; - })} + {opens.length > 0 ? ( + opens.map((doc) => ) + ) : ( +
{intl('No opened document')}
+ )}
); } diff --git a/packages/designer/src/project/project.less b/packages/designer/src/project/project.less index 673431498..fcfba65c8 100644 --- a/packages/designer/src/project/project.less +++ b/packages/designer/src/project/project.less @@ -4,6 +4,14 @@ right: 0; width: 100%; height: 100%; + .lc-project-empty { + width: 100%; + height: 100%; + font-size: 16px; + text-align: center; + background: transparent url(//img.alicdn.com/tfs/TB1xLKQAbj1gK0jSZFuXXcrHpXa-90-90.png) center 30% no-repeat; + padding-top: 50%; + } .lc-document { width: 100%; height: 100%; diff --git a/packages/designer/src/project/project.ts b/packages/designer/src/project/project.ts index 795358ab7..25ce16956 100644 --- a/packages/designer/src/project/project.ts +++ b/packages/designer/src/project/project.ts @@ -14,11 +14,11 @@ export class Project { // TODO: 考虑项目级别 History constructor(readonly designer: Designer, schema?: ProjectSchema) { - this.setSchema(schema); + this.load(schema); } @computed get currentDocument() { - return this.documents.find(doc => doc.actived); + return this.documents.find((doc) => doc.actived); } /** @@ -27,30 +27,45 @@ export class Project { getSchema(): ProjectSchema { return { ...this.data, - componentsTree: this.documents.map(doc => doc.schema), + // todo: future change this filter + componentsTree: this.documents.filter((doc) => !doc.isBlank()).map((doc) => doc.schema), }; } /** * 整体设置项目 schema + * + * @param autoOpen true 自动打开文档 string 指定打开的文件 */ - setSchema(schema?: ProjectSchema) { + load(schema?: ProjectSchema, autoOpen?: boolean | string) { + this.unload(); + // load new document this.data = { version: '1.0.0', componentsMap: [], componentsTree: [], ...schema, }; - if (this.documents.length > 0) { - this.documents.forEach(doc => doc.purge()); - this.documents.length = 0; + + if (autoOpen) { + if (autoOpen === true) { + // auto open first document or open a blank page + this.open(this.data.componentsTree[0]); + } else { + // auto open should be string of fileName + this.open(autoOpen); + } } - this.open( - this.data.componentsTree[0] || { - componentName: 'Page', - fileName: '', - }, - ); + } + + /** + * 卸载当前项目数据 + */ + unload() { + if (this.documents.length < 1) { + return; + } + this.documents.forEach((doc) => doc.remove()); } /** @@ -86,14 +101,23 @@ export class Project { | string, ): any {} - open(doc: string | DocumentModel | RootSchema): void { + open(doc?: string | DocumentModel | RootSchema): void { + if (!doc) { + const got = this.documents.find((item) => item.isBlank()); + if (got) { + return got.open(); + } + doc = new DocumentModel(this); + this.documents.push(doc); + return doc.open(); + } if (typeof doc === 'string') { - const got = this.documents.find(item => item.fileName === doc); + const got = this.documents.find((item) => item.fileName === doc); if (got) { return got.open(); } - const data = this.data.componentsTree.find(data => data.fileName === doc); + const data = this.data.componentsTree.find((data) => data.fileName === doc); if (data) { doc = new DocumentModel(this, data); this.documents.push(doc); @@ -113,7 +137,7 @@ export class Project { } checkExclusive(actived: DocumentModel) { - this.documents.forEach(doc => { + this.documents.forEach((doc) => { if (doc !== actived) { doc.suspense(); } @@ -122,7 +146,7 @@ export class Project { } closeOthers(opened: DocumentModel) { - this.documents.forEach(doc => { + this.documents.forEach((doc) => { if (doc !== opened) { doc.close(); } diff --git a/packages/editor-core/CHANGELOG.md b/packages/editor-core/CHANGELOG.md new file mode 100644 index 000000000..50762113f --- /dev/null +++ b/packages/editor-core/CHANGELOG.md @@ -0,0 +1,28 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## [0.8.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.3...@ali/lowcode-editor-core@0.8.4) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## 0.8.3 (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## 0.8.2 (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core diff --git a/packages/editor-core/package.json b/packages/editor-core/package.json index c5c46418c..813f34d15 100644 --- a/packages/editor-core/package.json +++ b/packages/editor-core/package.json @@ -1,6 +1,6 @@ { "name": "@ali/lowcode-editor-core", - "version": "0.8.0", + "version": "0.8.4", "description": "alibaba lowcode editor core", "main": "lib/index.js", "module": "es/index.js", @@ -20,15 +20,15 @@ ], "author": "xiayang.xy", "dependencies": { + "@alifd/next": "1.x", "debug": "^4.1.1", "events": "^3.1.0", "intl-messageformat": "^8.3.1", "lodash": "^4.17.15", "prop-types": "^15.5.8", - "store": "^2.0.12", - "whatwg-fetch": "^3.0.0", "react": "^16.8.0", - "@alifd/next": "1.x" + "store": "^2.0.12", + "whatwg-fetch": "^3.0.0" }, "devDependencies": { "@alib/build-scripts": "^0.1.3", diff --git a/packages/editor-core/src/editor.ts b/packages/editor-core/src/editor.ts index 6a0b938a0..6f9a1a01f 100644 --- a/packages/editor-core/src/editor.ts +++ b/packages/editor-core/src/editor.ts @@ -11,6 +11,8 @@ import { PluginSet, } from './definitions'; +import pluginFactory from './pluginFactory'; + import * as editorUtils from './utils'; const { registShortCuts, transformToPromise, unRegistShortCuts } = editorUtils; @@ -92,7 +94,10 @@ export default class Editor extends EventEmitter { constructor(config: EditorConfig, components: PluginClassSet, utils?: Utils) { super(); this.config = config; - this.components = components; + this.components = {}; + Object.entries(components).forEach(([key, value]): void => { + this.components[key] = pluginFactory(value); + }); this.utils = { ...editorUtils, ...utils }; instance = this; } diff --git a/packages/editor-core/tsconfig.json b/packages/editor-core/tsconfig.json index 67eaf09bf..c37b76ecc 100644 --- a/packages/editor-core/tsconfig.json +++ b/packages/editor-core/tsconfig.json @@ -1,9 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "outDir": "lib", - "target": "es6", - "jsx": "react" + "outDir": "lib" }, "include": [ "./src/" diff --git a/packages/editor-skeleton/CHANGELOG.md b/packages/editor-skeleton/CHANGELOG.md new file mode 100644 index 000000000..0a630a1c1 --- /dev/null +++ b/packages/editor-skeleton/CHANGELOG.md @@ -0,0 +1,43 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## [0.8.5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@0.8.4...@ali/lowcode-editor-skeleton@0.8.5) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-skeleton + + +## [0.8.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-skeleton@0.8.3...@ali/lowcode-editor-skeleton@0.8.4) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-skeleton + + +## 0.8.3 (2020-03-30) + + +### Features + +* 🎸 merge material-parser ([b40c286](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b40c286)) + + + + + +## 0.8.2 (2020-03-30) + + +### Features + +<<<<<<< HEAD +* 🎸 merge material-parser ([b40c286](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b40c286)) +======= +* 🎸 merge material-parser ([b40c286](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b40c2869a0bc901d855279735fe86b84dabaa04d)) +>>>>>>> df955e1db90ff104cd11160def80113cfd6faccc diff --git a/packages/editor-skeleton/package.json b/packages/editor-skeleton/package.json index 82cdf2609..1534fb47c 100644 --- a/packages/editor-skeleton/package.json +++ b/packages/editor-skeleton/package.json @@ -1,6 +1,6 @@ { "name": "@ali/lowcode-editor-skeleton", - "version": "0.8.0", + "version": "0.8.5", "description": "alibaba lowcode editor skeleton", "main": "lib/index.js", "module": "es/index.js", @@ -20,7 +20,7 @@ ], "author": "xiayang.xy", "dependencies": { - "@ali/lowcode-editor-core": "^0.8.0", + "@ali/lowcode-editor-core": "^0.8.4", "@alifd/next": "^1.x", "prop-types": "^15.5.8", "react": "^16.8.1", diff --git a/packages/editor-skeleton/tsconfig.json b/packages/editor-skeleton/tsconfig.json index 67eaf09bf..c37b76ecc 100644 --- a/packages/editor-skeleton/tsconfig.json +++ b/packages/editor-skeleton/tsconfig.json @@ -1,9 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "outDir": "lib", - "target": "es6", - "jsx": "react" + "outDir": "lib" }, "include": [ "./src/" diff --git a/packages/globals/CHANGELOG.md b/packages/globals/CHANGELOG.md new file mode 100644 index 000000000..1c72d35ae --- /dev/null +++ b/packages/globals/CHANGELOG.md @@ -0,0 +1,62 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## [0.9.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-globals@0.9.0...@ali/lowcode-globals@0.9.1) (2020-03-31) + + + + +**Note:** Version bump only for package @ali/lowcode-globals + + +# [0.9.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-globals@0.8.4...@ali/lowcode-globals@0.9.0) (2020-03-30) + + +### Features + +* **designer:** add builtin hotkeys ([2ec5883](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2ec5883)) + + + + + +## [0.8.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-globals@0.8.3...@ali/lowcode-globals@0.8.4) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-globals + + +## [0.8.3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-globals@0.8.2...@ali/lowcode-globals@0.8.3) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-globals + + +## 0.8.2 (2020-03-30) + + +### Features + +* double outline & ZH_EN support ([b379bd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b379bd7)) + + + + + +## 0.8.1 (2020-03-30) + + +### Features + +<<<<<<< HEAD +* double outline & ZH_EN support ([b379bd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b379bd7)) +======= +* double outline & ZH_EN support ([b379bd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b379bd7c0c488ef24f825760750a13d3fa083c96)) +>>>>>>> df955e1db90ff104cd11160def80113cfd6faccc diff --git a/packages/globals/package.json b/packages/globals/package.json index ee79abf6c..ac48bcc9a 100644 --- a/packages/globals/package.json +++ b/packages/globals/package.json @@ -1,7 +1,8 @@ { "name": "@ali/lowcode-globals", - "version": "0.8.0", + "version": "0.9.1", "description": "Globals api for Ali lowCode engine", + "license": "MIT", "main": "lib/index.js", "module": "es/index.js", "files": [ @@ -14,6 +15,16 @@ "test": "ava", "test:snapshot": "ava --update-snapshots" }, + "ava": { + "compileEnhancements": false, + "extensions": [ + "ts" + ], + "require": [ + "ts-node/register" + ], + "snapshotDir": "test/fixtures/__snapshots__" + }, "dependencies": { "@alifd/next": "^1.19.16", "@recore/obx": "^1.0.8", @@ -23,26 +34,15 @@ "react-dom": "^16.7.0" }, "devDependencies": { + "@alib/build-scripts": "^0.1.18", "@types/classnames": "^2.2.7", "@types/node": "^13.7.1", "@types/react": "^16", "@types/react-dom": "^16", - "@alib/build-scripts": "^0.1.18", "build-plugin-component": "^0.2.11", "build-plugin-fusion": "^0.1.0", "build-plugin-moment-locales": "^0.1.0" }, - "ava": { - "compileEnhancements": false, - "snapshotDir": "test/fixtures/__snapshots__", - "extensions": [ - "ts" - ], - "require": [ - "ts-node/register" - ] - }, - "license": "MIT", "publishConfig": { "registry": "https://registry.npm.alibaba-inc.com" } diff --git a/packages/globals/src/components/index.ts b/packages/globals/src/components/index.ts index f6eb9a1ff..196dee25a 100644 --- a/packages/globals/src/components/index.ts +++ b/packages/globals/src/components/index.ts @@ -1,2 +1,3 @@ export * from './tip'; export * from './title'; +export * from './svg-icon'; diff --git a/packages/globals/src/icons/icon-base.tsx b/packages/globals/src/components/svg-icon.tsx similarity index 96% rename from packages/globals/src/icons/icon-base.tsx rename to packages/globals/src/components/svg-icon.tsx index 778761ef0..f389606a8 100644 --- a/packages/globals/src/icons/icon-base.tsx +++ b/packages/globals/src/components/svg-icon.tsx @@ -16,7 +16,7 @@ export interface IconProps { style?: object; } -export function IconBase({ +export function SVGIcon({ fill, size = 'medium', viewBox, diff --git a/packages/globals/src/components/tip/tip-handler.ts b/packages/globals/src/components/tip/tip-handler.ts index ed1cb908a..db347f847 100644 --- a/packages/globals/src/components/tip/tip-handler.ts +++ b/packages/globals/src/components/tip/tip-handler.ts @@ -16,7 +16,7 @@ function findTip(target: HTMLElement | null): TipOptions | null { if (target.dataset && target.dataset.tip) { return { children: target.dataset.tip, - direction: target.dataset.direction || target.dataset.dir, + direction: (target.dataset.direction || target.dataset.dir) as any, theme: target.dataset.theme, target, }; diff --git a/packages/globals/src/icons/index.ts b/packages/globals/src/icons/index.ts deleted file mode 100644 index ac8767dc0..000000000 --- a/packages/globals/src/icons/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './clone'; -export * from './hidden'; -export * from './remove'; -export * from './setting'; -export * from './icon-base'; -export * from './component'; -export * from './container'; -export * from './page'; diff --git a/packages/globals/src/index.ts b/packages/globals/src/index.ts index cd2456f92..51d80c5da 100644 --- a/packages/globals/src/index.ts +++ b/packages/globals/src/index.ts @@ -1,7 +1,6 @@ export * from './intl'; export * from './components'; export * from './utils'; -export * from './icons'; export * from './types'; export * from './di'; export * from './obx'; diff --git a/packages/globals/src/types/tip.ts b/packages/globals/src/types/tip.ts index 0d9cd4752..5c7174159 100644 --- a/packages/globals/src/types/tip.ts +++ b/packages/globals/src/types/tip.ts @@ -5,7 +5,7 @@ export interface TipConfig { className?: string; children?: I18nData | ReactNode; theme?: string; - direction?: string; // 'n|s|w|e|top|bottom|left|right'; + direction?: 'top' | 'bottom' | 'left' | 'right'; } export type TipContent = string | I18nData | ReactElement | TipConfig; diff --git a/packages/globals/src/utils/hotkey.ts b/packages/globals/src/utils/hotkey.ts new file mode 100644 index 000000000..570e13ebe --- /dev/null +++ b/packages/globals/src/utils/hotkey.ts @@ -0,0 +1,604 @@ +interface KeyMap { + [key: number]: string; +} + +interface CtrlKeyMap { + [key: string]: string; +} + +interface ActionEvent { + type: string; +} + +interface HotkeyCallbacks { + [key: string]: HotkeyCallbackCfg[]; +} + +interface HotkeyDirectMap { + [key: string]: HotkeyCallback; +} + +export type HotkeyCallback = (e: KeyboardEvent, combo?: string) => any | false; + +interface HotkeyCallbackCfg { + callback: HotkeyCallback; + modifiers: string[]; + action: string; + seq?: string; + level?: number; + combo?: string; +} + +interface KeyInfo { + key: string; + modifiers: string[]; + action: string; +} + +interface SequenceLevels { + [key: string]: number; +} + +const MAP: KeyMap = { + 8: 'backspace', + 9: 'tab', + 13: 'enter', + 16: 'shift', + 17: 'ctrl', + 18: 'alt', + 20: 'capslock', + 27: 'esc', + 32: 'space', + 33: 'pageup', + 34: 'pagedown', + 35: 'end', + 36: 'home', + 37: 'left', + 38: 'up', + 39: 'right', + 40: 'down', + 45: 'ins', + 46: 'del', + 91: 'meta', + 93: 'meta', + 224: 'meta', +}; + +const KEYCODE_MAP: KeyMap = { + 106: '*', + 107: '+', + 109: '-', + 110: '.', + 111: '/', + 186: ';', + 187: '=', + 188: ',', + 189: '-', + 190: '.', + 191: '/', + 192: '`', + 219: '[', + 220: '\\', + 221: ']', + 222: "'", +}; + +const SHIFT_MAP: CtrlKeyMap = { + '~': '`', + '!': '1', + '@': '2', + '#': '3', + $: '4', + '%': '5', + '^': '6', + '&': '7', + '*': '8', + '(': '9', + ')': '0', + _: '-', + '+': '=', + ':': ';', + '"': "'", + '<': ',', + '>': '.', + '?': '/', + '|': '\\', +}; + +const SPECIAL_ALIASES: CtrlKeyMap = { + option: 'alt', + command: 'meta', + return: 'enter', + escape: 'esc', + plus: '+', + mod: /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'meta' : 'ctrl', +}; + +let REVERSE_MAP: CtrlKeyMap; + +/** + * loop through the f keys, f1 to f19 and add them to the map + * programatically + */ +for (let i = 1; i < 20; ++i) { + MAP[111 + i] = 'f' + i; +} + +/** + * loop through to map numbers on the numeric keypad + */ +for (let i = 0; i <= 9; ++i) { + MAP[i + 96] = String(i); +} + +/** + * takes the event and returns the key character + */ +function characterFromEvent(e: KeyboardEvent): string { + const keyCode = e.keyCode || e.which; + // for keypress events we should return the character as is + if (e.type === 'keypress') { + let character = String.fromCharCode(keyCode); + // if the shift key is not pressed then it is safe to assume + // that we want the character to be lowercase. this means if + // you accidentally have caps lock on then your key bindings + // will continue to work + // + // the only side effect that might not be desired is if you + // bind something like 'A' cause you want to trigger an + // event when capital A is pressed caps lock will no longer + // trigger the event. shift+a will though. + if (!e.shiftKey) { + character = character.toLowerCase(); + } + return character; + } + // for non keypress events the special maps are needed + if (MAP[keyCode]) { + return MAP[keyCode]; + } + if (KEYCODE_MAP[keyCode]) { + return KEYCODE_MAP[keyCode]; + } + // if it is not in the special map + // with keydown and keyup events the character seems to always + // come in as an uppercase character whether you are pressing shift + // or not. we should make sure it is always lowercase for comparisons + return String.fromCharCode(keyCode).toLowerCase(); +} + +interface KeypressEvent extends KeyboardEvent { + type: 'keypress'; +} + +function isPressEvent(e: KeyboardEvent | ActionEvent): e is KeypressEvent { + return e.type === 'keypress'; +} + +/** + * checks if two arrays are equal + */ +function modifiersMatch(modifiers1: string[], modifiers2: string[]): boolean { + return modifiers1.sort().join(',') === modifiers2.sort().join(','); +} + +/** + * takes a key event and figures out what the modifiers are + */ +function eventModifiers(e: KeyboardEvent): string[] { + const modifiers = []; + + if (e.shiftKey) { + modifiers.push('shift'); + } + + if (e.altKey) { + modifiers.push('alt'); + } + + if (e.ctrlKey) { + modifiers.push('ctrl'); + } + + if (e.metaKey) { + modifiers.push('meta'); + } + + return modifiers; +} + +/** + * determines if the keycode specified is a modifier key or not + */ +function isModifier(key: string): boolean { + return key === 'shift' || key === 'ctrl' || key === 'alt' || key === 'meta'; +} + +/** + * reverses the map lookup so that we can look for specific keys + * to see what can and can't use keypress + * + * @return {Object} + */ +function getReverseMap(): CtrlKeyMap { + if (!REVERSE_MAP) { + REVERSE_MAP = {}; + for (const key in MAP) { + // pull out the numeric keypad from here cause keypress should + // be able to detect the keys from the character + if (Number(key) > 95 && Number(key) < 112) { + continue; + } + + if (MAP.hasOwnProperty(key)) { + REVERSE_MAP[MAP[key]] = key; + } + } + } + return REVERSE_MAP; +} + +/** + * picks the best action based on the key combination + */ +function pickBestAction(key: string, modifiers: string[], action?: string): string { + // if no action was picked in we should try to pick the one + // that we think would work best for this key + if (!action) { + action = getReverseMap()[key] ? 'keydown' : 'keypress'; + } + // modifier keys don't work as expected with keypress, + // switch to keydown + if (action === 'keypress' && modifiers.length) { + action = 'keydown'; + } + return action; +} + +/** + * Converts from a string key combination to an array + * + * @param {string} combination like "command+shift+l" + * @return {Array} + */ +function keysFromString(combination: string): string[] { + if (combination === '+') { + return ['+']; + } + + combination = combination.replace(/\+{2}/g, '+plus'); + return combination.split('+'); +} + +/** + * Gets info for a specific key combination + * + * @param combination key combination ("command+s" or "a" or "*") + */ +function getKeyInfo(combination: string, action?: string): KeyInfo { + let keys: string[] = []; + let key = ''; + let i: number; + const modifiers: string[] = []; + + // take the keys from this pattern and figure out what the actual + // pattern is all about + keys = keysFromString(combination); + + for (i = 0; i < keys.length; ++i) { + key = keys[i]; + + // normalize key names + if (SPECIAL_ALIASES[key]) { + key = SPECIAL_ALIASES[key]; + } + + // if this is not a keypress event then we should + // be smart about using shift keys + // this will only work for US keyboards however + if (action && action !== 'keypress' && SHIFT_MAP[key]) { + key = SHIFT_MAP[key]; + modifiers.push('shift'); + } + + // if this key is a modifier then add it to the list of modifiers + if (isModifier(key)) { + modifiers.push(key); + } + } + + // depending on what the key combination is + // we will try to pick the best event for it + action = pickBestAction(key, modifiers, action); + + return { + key, + modifiers, + action, + }; +} + +/** + * actually calls the callback function + * + * if your callback function returns false this will use the jquery + * convention - prevent default and stop propogation on the event + */ +function fireCallback(callback: HotkeyCallback, e: KeyboardEvent, combo?: string, sequence?: string): void { + if (callback(e, combo) === false) { + e.preventDefault(); + e.stopPropagation(); + } +} + +export class Hotkey { + private callBacks: HotkeyCallbacks = {}; + private directMap: HotkeyDirectMap = {}; + private sequenceLevels: SequenceLevels = {}; + private resetTimer = 0; + private ignoreNextKeyup: boolean | string = false; + private ignoreNextKeypress = false; + private nextExpectedAction: boolean | string = false; + + mount(window: Window) { + const document = window.document; + const handleKeyEvent = this.handleKeyEvent.bind(this); + document.addEventListener('keypress', handleKeyEvent, false); + document.addEventListener('keydown', handleKeyEvent, false); + document.addEventListener('keyup', handleKeyEvent, false); + return () => { + document.removeEventListener('keypress', handleKeyEvent, false); + document.removeEventListener('keydown', handleKeyEvent, false); + document.removeEventListener('keyup', handleKeyEvent, false); + }; + } + + bind(combos: string[] | string, callback: HotkeyCallback, action?: string): Hotkey { + this.bindMultiple(Array.isArray(combos) ? combos : [combos], callback, action); + return this; + } + + /** + * resets all sequence counters except for the ones passed in + */ + private resetSequences(doNotReset?: SequenceLevels): void { + // doNotReset = doNotReset || {}; + let activeSequences = false; + let key = ''; + for (key in this.sequenceLevels) { + if (doNotReset && doNotReset[key]) { + activeSequences = true; + } else { + this.sequenceLevels[key] = 0; + } + } + if (!activeSequences) { + this.nextExpectedAction = false; + } + } + + /** + * finds all callbacks that match based on the keycode, modifiers, + * and action + */ + private getMatches( + character: string, + modifiers: string[], + e: KeyboardEvent | ActionEvent, + sequenceName?: string, + combination?: string, + level?: number, + ): HotkeyCallbackCfg[] { + let i: number; + let callback: HotkeyCallbackCfg; + const matches: HotkeyCallbackCfg[] = []; + const action: string = e.type; + + // if there are no events related to this keycode + if (!this.callBacks[character]) { + return []; + } + + // if a modifier key is coming up on its own we should allow it + if (action === 'keyup' && isModifier(character)) { + modifiers = [character]; + } + + // loop through all callbacks for the key that was pressed + // and see if any of them match + for (i = 0; i < this.callBacks[character].length; ++i) { + callback = this.callBacks[character][i]; + + // if a sequence name is not specified, but this is a sequence at + // the wrong level then move onto the next match + if (!sequenceName && callback.seq && this.sequenceLevels[callback.seq] !== callback.level) { + continue; + } + + // if the action we are looking for doesn't match the action we got + // then we should keep going + if (action !== callback.action) { + continue; + } + + // if this is a keypress event and the meta key and control key + // are not pressed that means that we need to only look at the + // character, otherwise check the modifiers as well + // + // chrome will not fire a keypress if meta or control is down + // safari will fire a keypress if meta or meta+shift is down + // firefox will fire a keypress if meta or control is down + if ((isPressEvent(e) && !e.metaKey && !e.ctrlKey) || modifiersMatch(modifiers, callback.modifiers)) { + const deleteCombo = !sequenceName && callback.combo === combination; + const deleteSequence = sequenceName && callback.seq === sequenceName && callback.level === level; + if (deleteCombo || deleteSequence) { + this.callBacks[character].splice(i, 1); + } + + matches.push(callback); + } + } + return matches; + } + + private handleKey(character: string, modifiers: string[], e: KeyboardEvent): void { + const callbacks: HotkeyCallbackCfg[] = this.getMatches(character, modifiers, e); + let i: number; + const doNotReset: SequenceLevels = {}; + let maxLevel = 0; + let processedSequenceCallback = false; + + // Calculate the maxLevel for sequences so we can only execute the longest callback sequence + for (i = 0; i < callbacks.length; ++i) { + if (callbacks[i].seq) { + maxLevel = Math.max(maxLevel, callbacks[i].level || 0); + } + } + + // loop through matching callbacks for this key event + for (i = 0; i < callbacks.length; ++i) { + // fire for all sequence callbacks + // this is because if for example you have multiple sequences + // bound such as "g i" and "g t" they both need to fire the + // callback for matching g cause otherwise you can only ever + // match the first one + if (callbacks[i].seq) { + // only fire callbacks for the maxLevel to prevent + // subsequences from also firing + // + // for example 'a option b' should not cause 'option b' to fire + // even though 'option b' is part of the other sequence + // + // any sequences that do not match here will be discarded + // below by the resetSequences call + if (callbacks[i].level !== maxLevel) { + continue; + } + + processedSequenceCallback = true; + + // keep a list of which sequences were matches for later + doNotReset[callbacks[i].seq || ''] = 1; + fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq); + continue; + } + + // if there were no sequence matches but we are still here + // that means this is a regular match so we should fire that + if (!processedSequenceCallback) { + fireCallback(callbacks[i].callback, e, callbacks[i].combo); + } + } + + const ignoreThisKeypress = e.type === 'keypress' && this.ignoreNextKeypress; + if (e.type === this.nextExpectedAction && !isModifier(character) && !ignoreThisKeypress) { + this.resetSequences(doNotReset); + } + + this.ignoreNextKeypress = processedSequenceCallback && e.type === 'keydown'; + } + + private handleKeyEvent(e: KeyboardEvent): void { + const character = characterFromEvent(e); + + // no character found then stop + if (!character) { + return; + } + + // need to use === for the character check because the character can be 0 + if (e.type === 'keyup' && this.ignoreNextKeyup === character) { + this.ignoreNextKeyup = false; + return; + } + + this.handleKey(character, eventModifiers(e), e); + } + + private resetSequenceTimer(): void { + if (this.resetTimer) { + clearTimeout(this.resetTimer); + } + this.resetTimer = window.setTimeout(this.resetSequences, 1000); + } + + private bindSequence(combo: string, keys: string[], callback: HotkeyCallback, action?: string): void { + // const self: any = this; + this.sequenceLevels[combo] = 0; + const increaseSequence = (nextAction: string) => { + return () => { + this.nextExpectedAction = nextAction; + ++this.sequenceLevels[combo]; + this.resetSequenceTimer(); + }; + }; + const callbackAndReset = (e: KeyboardEvent): void => { + fireCallback(callback, e, combo); + + if (action !== 'keyup') { + this.ignoreNextKeyup = characterFromEvent(e); + } + + setTimeout(this.resetSequences, 10); + }; + for (let i = 0; i < keys.length; ++i) { + const isFinal = i + 1 === keys.length; + const wrappedCallback = isFinal ? callbackAndReset : increaseSequence(action || getKeyInfo(keys[i + 1]).action); + this.bindSingle(keys[i], wrappedCallback, action, combo, i); + } + } + + private bindSingle( + combination: string, + callback: HotkeyCallback, + action?: string, + sequenceName?: string, + level?: number, + ): void { + // store a direct mapped reference for use with HotKey.trigger + this.directMap[`${combination}:${action}`] = callback; + + // make sure multiple spaces in a row become a single space + combination = combination.replace(/\s+/g, ' '); + + const sequence: string[] = combination.split(' '); + let info: KeyInfo; + + // if this pattern is a sequence of keys then run through this method + // to reprocess each pattern one key at a time + if (sequence.length > 1) { + this.bindSequence(combination, sequence, callback, action); + return; + } + + info = getKeyInfo(combination, action); + + // make sure to initialize array if this is the first time + // a callback is added for this key + this.callBacks[info.key] = this.callBacks[info.key] || []; + + // remove an existing match if there is one + this.getMatches(info.key, info.modifiers, { type: info.action }, sequenceName, combination, level); + + // add this call back to the array + // if it is a sequence put it at the beginning + // if not put it at the end + // + // this is important because the way these are processed expects + // the sequence ones to come first + this.callBacks[info.key][sequenceName ? 'unshift' : 'push']({ + callback, + modifiers: info.modifiers, + action: info.action, + seq: sequenceName, + level, + combo: combination, + }); + } + + private bindMultiple(combinations: string[], callback: HotkeyCallback, action?: string) { + for (const item of combinations) { + this.bindSingle(item, callback, action); + } + } +} diff --git a/packages/globals/src/utils/index.ts b/packages/globals/src/utils/index.ts index b1583025d..c61e07cd1 100644 --- a/packages/globals/src/utils/index.ts +++ b/packages/globals/src/utils/index.ts @@ -19,3 +19,4 @@ export * from './shallow-equal'; export * from './unique-id'; export * from './get-public-path'; export * from './is-form-event'; +export * from './hotkey'; diff --git a/packages/material-parser/CHANGELOG.md b/packages/material-parser/CHANGELOG.md new file mode 100644 index 000000000..cc9d6a4cf --- /dev/null +++ b/packages/material-parser/CHANGELOG.md @@ -0,0 +1,118 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +# [0.9.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-material-parser@0.8.4...@ali/lowcode-material-parser@0.9.0) (2020-03-31) + + +### Bug Fixes + +* [material-parser]fix bug of main field & remove useless debugger ([8fde0ec](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8fde0ec)) +* fix bug of build errors ([770a1b6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/770a1b6)) +* fix bug of missing types in material-parser ([9ce0a73](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/9ce0a73)) + + +### Features + +* 🎸 support parsing fusion source code ([5895cf1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/5895cf1)) +* support localizing ([e1faa84](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e1faa84)) + + + + + +## [0.8.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-material-parser@0.8.3...@ali/lowcode-material-parser@0.8.4) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-material-parser + + +## [0.8.3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-material-parser@0.8.2...@ali/lowcode-material-parser@0.8.3) (2020-03-30) + + + + +**Note:** Version bump only for package @ali/lowcode-material-parser + + +## 0.8.2 (2020-03-30) + + +### Bug Fixes + +* 🐛 fix bug of transforming type ([ebbe58d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ebbe58d)) +* 🐛 fix bug of validate schema ([3f97523](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3f97523)) + + +### Code Refactoring + +* 💡 refactor with react-docgen ([64c9daa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/64c9daa)) + + +### Features + +* complete component protocol json schema & validate method ([3df360d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3df360d)) +* immigrate aimake materialin ([44ac85f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/44ac85f)) +* import react-docgen to parse propTypes ([6e66168](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6e66168)) +* remove -p tslint.json for test ([6d013e1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6d013e1)) +* remove useless codes & modify generator ([dcd1b33](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dcd1b33)) +* support multiple exported components ([db1b6de](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/db1b6de)) + + +### BREAKING CHANGES + +* 🧨 use react-docgen to replace parser + + + + + +## 0.8.1 (2020-03-30) + + +### Bug Fixes + +<<<<<<< HEAD +* 🐛 fix bug of transforming type ([ebbe58d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ebbe58d)) +* 🐛 fix bug of validate schema ([3f97523](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3f97523)) +======= +* 🐛 fix bug of transforming type ([ebbe58d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ebbe58df70f047f4b5fe367ac4b4a08de8a65e5d)) +* 🐛 fix bug of validate schema ([3f97523](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3f975232c7cd551bc9c74962095dcc9b127af489)) +>>>>>>> df955e1db90ff104cd11160def80113cfd6faccc + + +### Code Refactoring + +<<<<<<< HEAD +* 💡 refactor with react-docgen ([64c9daa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/64c9daa)) +======= +* 💡 refactor with react-docgen ([64c9daa](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/64c9daa1f451fdfeab2777e4beefc5d5e1890ba1)) +>>>>>>> df955e1db90ff104cd11160def80113cfd6faccc + + +### Features + +<<<<<<< HEAD +* complete component protocol json schema & validate method ([3df360d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3df360d)) +* immigrate aimake materialin ([44ac85f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/44ac85f)) +* import react-docgen to parse propTypes ([6e66168](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6e66168)) +* remove -p tslint.json for test ([6d013e1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6d013e1)) +* remove useless codes & modify generator ([dcd1b33](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dcd1b33)) +* support multiple exported components ([db1b6de](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/db1b6de)) +======= +* complete component protocol json schema & validate method ([3df360d](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3df360de85d425b2926ea50ff26a8df27ec36a78)) +* immigrate aimake materialin ([44ac85f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/44ac85f8a6a35bcd50f2e2b74a022e3cebe3cdef)) +* import react-docgen to parse propTypes ([6e66168](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6e661686e4693e69279c496f3be1dd173703c55e)) +* remove -p tslint.json for test ([6d013e1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6d013e18f93bad5647cb9ea0a497336f64e1459a)) +* remove useless codes & modify generator ([dcd1b33](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/dcd1b33d3bf8bdf5577dcc980608d9eac8d99372)) +* support multiple exported components ([db1b6de](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/db1b6deaca256b0d107fe607de6cd0fc90517a9c)) +>>>>>>> df955e1db90ff104cd11160def80113cfd6faccc + + +### BREAKING CHANGES + +* 🧨 use react-docgen to replace parser diff --git a/packages/material-parser/package.json b/packages/material-parser/package.json index 3b1314827..dab853438 100644 --- a/packages/material-parser/package.json +++ b/packages/material-parser/package.json @@ -1,6 +1,6 @@ { "name": "@ali/lowcode-material-parser", - "version": "0.8.0", + "version": "0.9.0", "description": "material parser for Ali lowCode engine", "main": "lib/index.js", "files": [ @@ -20,6 +20,7 @@ "jest-watch-typeahead": "^0.3.1", "js-yaml": "^3.13.1", "json-schema-to-typescript": "^8.2.0", + "tslib": "^1.11.1", "typescript": "^3.8.3" }, "scripts": { @@ -47,6 +48,7 @@ "@babel/parser": "^7.8.4", "@babel/traverse": "^7.8.4", "@babel/types": "^7.8.3", + "ast-types": "^0.13.3", "cross-spawn-promise": "^0.10.2", "debug": "^4.1.1", "fs-extra": "^8.1.0", @@ -54,5 +56,8 @@ "react-docgen": "^5.3.0", "semver": "^7.1.3", "short-uuid": "^3.1.1" + }, + "publishConfig": { + "registry": "https://registry.npm.alibaba-inc.com" } } diff --git a/packages/material-parser/scripts/transform.js b/packages/material-parser/scripts/transform.js index b07f55aa1..2f9416675 100644 --- a/packages/material-parser/scripts/transform.js +++ b/packages/material-parser/scripts/transform.js @@ -17,7 +17,7 @@ const tsPath = path.resolve(__dirname, '../src/otter-core/schema/types.ts'); ajv.compile(schema); fs.writeFileSync(JsonPath, JSON.stringify(schema, null, 2), 'utf-8'); console.log('yaml file is successfully transformed into json'); - const ts = await compile(schema, 'IComponentMaterial'); + const ts = await compile(schema, 'ComponentMeta'); fs.writeFileSync(tsPath, ts); console.log('schema.d.ts is successfully generated'); } catch (e) { diff --git a/packages/material-parser/src/Materialize.ts b/packages/material-parser/src/Materialize.ts deleted file mode 100644 index 06928f804..000000000 --- a/packages/material-parser/src/Materialize.ts +++ /dev/null @@ -1,48 +0,0 @@ -import LocalAccesser from './accesser/LocalAccesser'; -import OnlineAccesser from './accesser/OnlineAccesser'; -import { IComponentMaterial } from './otter-core'; -import { IAccesser, IMaterializeOptions } from './types'; - -/** - * 物料化(将普通组件包装为可接入和可流通的物料组件的过程,称为物料化)运行于 node 端 - * @class Materialize - */ -class Materialize { - /** - * 物料化配置项 - * @private - * @type {IMaterializeOptions} - * @memberof Materialize - */ - private options: IMaterializeOptions; - - /** - * 接入器 - * @private - * @type {IAccesser} - * @memberof Materialize - */ - private accesser?: IAccesser; - - constructor(options: IMaterializeOptions) { - this.options = options; - } - - /** - * 开始物料化 - * - * @returns {Promise} - * @memberof Materialize - */ - public async start(): Promise { - // 分发请求到对应接入器 - if (this.options.accesser === 'local') { - this.accesser = new LocalAccesser(this.options); - } else { - this.accesser = new OnlineAccesser(this.options); - } - return this.accesser.access(); - } -} - -export default Materialize; diff --git a/packages/material-parser/src/accesser/BaseAccesser.ts b/packages/material-parser/src/accesser/BaseAccesser.ts deleted file mode 100644 index 40269a88d..000000000 --- a/packages/material-parser/src/accesser/BaseAccesser.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { IComponentMaterial } from '../otter-core'; -import { IAccesser, IMaterializeOptions } from '../types'; - -/** - * 接入器模板基类 - * @abstract - * @class BaseAccesser - * @implements {IAccesser} - */ -abstract class BaseAccesser implements IAccesser { - /** - * 物料化配置项 - * @protected - * @type {IMaterializeOptions} - * @memberof BaseAccesser - */ - protected options: IMaterializeOptions; - - constructor(options: IMaterializeOptions) { - this.options = options; - } - - public abstract access(): Promise; -} - -export default BaseAccesser; diff --git a/packages/material-parser/src/accesser/LocalAccesser.ts b/packages/material-parser/src/accesser/LocalAccesser.ts deleted file mode 100644 index e3bc885ce..000000000 --- a/packages/material-parser/src/accesser/LocalAccesser.ts +++ /dev/null @@ -1,78 +0,0 @@ -import Generator from '../generator/Generator'; -import { debug, IComponentMaterial } from '../otter-core'; -import BaseParser from '../parser/BaseParser'; -import ReactParser from '../parser/ReactParser'; -import Scanner from '../scanner/Scanner'; -import { IMaterialParsedModel, IMaterialScanModel, IParser } from '../types'; -import BaseAccesser from './BaseAccesser'; - -const log = debug.extend('mat'); - -/** - * 本地接入器 - * @class LocalAccesser - * @extends {BaseAccesser} - */ -class LocalAccesser extends BaseAccesser { - /** - * 扫描器实例 - * @private - * @type {Scanner} - * @memberof LocalAccesser - */ - private scanner!: Scanner; - - /** - * 解析器实例 - * @private - * @type {IParser} - * @memberof LocalAccesser - */ - private parser!: IParser; - - /** - * 生成器实例 - * @private - * @type {Generator} - * @memberof LocalAccesser - */ - private generator!: Generator; - - public async access(): Promise { - await this.init(); - // 开始扫描 - const matScanModel: IMaterialScanModel = await this.scanner.scan(); - log('matScanModel', matScanModel); - // 开始解析 - const matParsedModels: IMaterialParsedModel[] = await this.parser.parse( - matScanModel, - ); - log('matParsedModels', matParsedModels); - // 开始生产 - const material: IComponentMaterial = await this.generator.generate( - matScanModel, - matParsedModels, - ); - log('material', material); - return material; - } - - /** - * 初始化 - * @private - * @returns {Promise} - * @memberof LocalAccesser - */ - private async init(): Promise { - const options = this.options; - this.scanner = new Scanner(options); - const ecology = await BaseParser.recognizeEcology(options); - if (ecology === 'react') { - // debugger; - this.parser = new ReactParser(options); - this.generator = new Generator(options); - } - } -} - -export default LocalAccesser; diff --git a/packages/material-parser/src/accesser/OnlineAccesser.ts b/packages/material-parser/src/accesser/OnlineAccesser.ts deleted file mode 100644 index 160f4d20b..000000000 --- a/packages/material-parser/src/accesser/OnlineAccesser.ts +++ /dev/null @@ -1,121 +0,0 @@ -import spawn from 'cross-spawn-promise'; -import { ensureDir, ensureFile, writeFile } from 'fs-extra'; -import { join } from 'path'; -import semver from 'semver'; -import uuid from 'short-uuid'; -import { debug, IComponentMaterial, OtterError } from '../otter-core'; -import { IMaterializeOptions } from '../types'; -import BaseAccesser from './BaseAccesser'; -import LocalAccesser from './LocalAccesser'; - -const log = debug.extend('mat'); - -/** - * 在线接入 - * @class OnlineAccesser - * @extends {BaseAccesser} - */ -class OnlineAccesser extends BaseAccesser { - /** - * 临时目录 - * - * @private - * @type {string} - * @memberof OnlineAccesser - */ - private tempDir: string = ''; - - public async access(): Promise { - // 创建临时目录 - this.tempDir = await this.createTempDir(); - // 创建组件包 - const { name, version } = this.getPkgNameAndVersion(this.options.entry); - await this.createFakePackage({ - pkgName: name, - pkgVersion: version, - }); - // 将问题转化为本地物料化场景 - const options: IMaterializeOptions = { - cwd: this.tempDir, - entry: join(this.tempDir, 'node_modules', name), - accesser: 'local', - isExportedAsMultiple: this.options.isExportedAsMultiple, - }; - const localAccesser = new LocalAccesser(options); - return localAccesser.access(); - } - - /** - * 创建组件包 - * - * @private - * @param {{ - * pkgName: string; - * pkgVersion: string; - * }} params - * @returns {Promise} - * @memberof OnlineAccesser - */ - private async createFakePackage(params: { - pkgName: string; - pkgVersion: string; - }): Promise { - // 创建临时组件包 - const tempDir = this.tempDir; - const pkgJsonFilePath = join(tempDir, 'package.json'); - await ensureFile(pkgJsonFilePath); - await writeFile( - pkgJsonFilePath, - JSON.stringify({ - name: params.pkgName, - version: params.pkgVersion, - dependencies: { - [params.pkgName]: params.pkgVersion, - }, - }), - ); - // 安装依赖 - const npmClient = this.options.npmClient || 'tnpm'; - await spawn(npmClient, ['i'], { stdio: 'inherit', cwd: tempDir } as any); - } - - /** - * 创建临时目录 - * - * @private - * @returns {Promise} 返回临时文件夹路径 - * @memberof LocalGenerator - */ - private async createTempDir(): Promise { - const tempDirName = uuid.generate(); - const tempDir = join(__dirname, '../../node_modules/.temp/', tempDirName); - await ensureDir(tempDir); - log('create temp dir successfully', tempDir); - return tempDir; - } - - /** - * 分离物料组件名称和版本号 - * - * @private - * @param {string} pkgNameWithVersion - * @returns {{ [key: string]: any }} - * @memberof OnlineAccesser - */ - private getPkgNameAndVersion( - pkgNameWithVersion: string, - ): { [key: string]: any } { - const matches = pkgNameWithVersion.match(/(@\d+\.\d+\.\d+)$/); - if (!matches) { - throw new OtterError(`Illegal semver version: ${pkgNameWithVersion}`); - } - const semverObj = semver.coerce(matches[0]); - const name = pkgNameWithVersion.replace(matches[0], ''); - return { - version: semverObj && semverObj.version, - name, - }; - } -} - -export default OnlineAccesser; diff --git a/packages/material-parser/src/extensions/MatConfigManifest.ts b/packages/material-parser/src/extensions/MatConfigManifest.ts deleted file mode 100644 index 491fdce8f..000000000 --- a/packages/material-parser/src/extensions/MatConfigManifest.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { writeFile } from 'fs-extra'; -import { IComponentMaterial } from '../otter-core'; - -/** - * 配置 manifest(物料化场景下可以使用此扩展点) - * - 扩展点名称:mat:config:manifest - * - 对应 Studio 所处状态:进入 就绪态 前 - * - * @export - * @param {{ - * manifestObj: IComponentMaterial, - * manifestFilePath: string, - * }} params - * @returns {Promise<{ - * manifestJS: string, - * manifestFilePath: string, - * manifestObj: IComponentMaterial, - * }>} - */ -export default async function matConfigManifest(params: { - manifestObj: IComponentMaterial; - manifestFilePath: string; -}): Promise<{ - manifestJSON: string; - manifestFilePath: string; - manifestObj: IComponentMaterial; -}> { - const manifestJSON = JSON.stringify(params.manifestObj); - - await writeFile(params.manifestFilePath, manifestJSON); - - return Promise.resolve({ - manifestJSON, - manifestObj: params.manifestObj, - manifestFilePath: params.manifestFilePath, - }); -} diff --git a/packages/material-parser/src/extensions/index.ts b/packages/material-parser/src/extensions/index.ts deleted file mode 100644 index d35c1b720..000000000 --- a/packages/material-parser/src/extensions/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ExtensionName } from '../types'; -import MatConfigManifest from './MatConfigManifest'; - -export default { - [ExtensionName.CONFIGMANIFEST]: MatConfigManifest, -}; diff --git a/packages/material-parser/src/generate.ts b/packages/material-parser/src/generate.ts new file mode 100644 index 000000000..6c4f0b140 --- /dev/null +++ b/packages/material-parser/src/generate.ts @@ -0,0 +1,59 @@ +import { debug, ComponentMeta } from './otter-core'; +import { IMaterialParsedModel, IMaterialScanModel } from './types'; + +const log = debug.extend('mat'); + +export default async function( + matScanModel: IMaterialScanModel, + matParsedModels: IMaterialParsedModel[], +): Promise { + const containerList = []; + for (const matParsedModel of matParsedModels) { + // TODO 可以开放扩展点让上层使用者指定导出哪些组件或者不导出哪些组件 + // 默认排除掉 defaultExportName 为空的组件 + if (!matParsedModel.componentName) { + log('skip'); + continue; + } + // 组装 manifest + const manifest: any = await genManifest(matScanModel, matParsedModel); + + containerList.push(manifest); + } + + return containerList; +} + +/** + * 生成 manifest + * + * @param {IMaterialParsedModel} matParsedModel + * @returns {Promise< + * manifestObj: ComponentMeta, // 组件描述 + * >} + * @memberof LocalGenerator + */ +export async function genManifest( + matScanModel: IMaterialScanModel, + matParsedModel: IMaterialParsedModel, +): Promise { + const manifestObj: Partial = { + componentName: matParsedModel.componentName, + title: matScanModel.pkgName, + docUrl: '', + screenshot: '', + npm: { + package: matScanModel.pkgName, + version: matScanModel.pkgVersion, + exportName: matParsedModel.componentName, + main: matScanModel.mainFilePath, + destructuring: false, + subName: '', + }, + }; + + // 填充 props + manifestObj.props = matParsedModel.props; + // 执行扩展点 + return manifestObj as ComponentMeta; +} diff --git a/packages/material-parser/src/generator/Generator.ts b/packages/material-parser/src/generator/Generator.ts deleted file mode 100644 index bec97c827..000000000 --- a/packages/material-parser/src/generator/Generator.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { dirname, join } from 'path'; -import defaultExtension from '../extensions'; -import { debug, IComponentMaterial, PropsSection } from '../otter-core'; -import { - IGenerator, - IMaterializeOptions, - IMaterialParsedModel, - IMaterialScanModel, -} from '../types'; - -const log = debug.extend('mat'); - -/** - * 用于物料工作台 - */ -class Generator implements IGenerator { - /** - * 物料化配置项 - * @protected - * @type {IMaterializeOptions} - * @memberof BaseGenerator - */ - /** - * 物料化配置项 - * @protected - * @type {IMaterializeOptions} - * @memberof BaseGenerator - */ - protected options!: IMaterializeOptions; - - constructor(options: IMaterializeOptions) { - this.options = options; - } - - public async generate( - matScanModel: IMaterialScanModel, - matParsedModels: IMaterialParsedModel[], - ): Promise { - // const model: IMaterialinSchema = {} as any; - // 标记协议版本号 - // model.version = '1.0.0'; - // 组装 pkgInfo - // model.pkgInfo = pkgInfo; - const containerList = []; - for (const matParsedModel of matParsedModels) { - // TODO 可以开放扩展点让上层使用者指定导出哪些组件或者不导出哪些组件 - // 默认排除掉 defaultExportName 为空的组件 - if ( - !matParsedModel.defaultExportName || - !matParsedModel.defaultExportName.length - ) { - log('skip', matParsedModel.filePath); - continue; - } - // 组装 manifest - const manifest: any = await this.genManifest( - matScanModel, - matParsedModel, - ); - - containerList.push(manifest); - } - - // const components: IMaterialinComponent[] = bundle.bundleObj.components; - // Object.keys(bundle.bundleObj.Modules).forEach(key => { - // const { origin, manifest } = bundle.bundleObj.Modules[key]; - // const component: IMaterialinComponent = { - // componentName: key, - // origin, - // manifest, - // }; - // components.push(component); - // }); - // model.components = components; - // log('materialsModel', JSON.stringify(bundle.bundleObj)); - - return containerList; - } - - /** - * 生成 manifest - * - * @param {IMaterialParsedModel} matParsedModel - * @returns {Promise<{ - * manifestFilePath: string, // manifest 文件路径 - * manifestJS: string, // manifest 文件内容 - * manifestObj: IMaterialinManifest, // manifest 文件对象 - * }>} - * @memberof LocalGenerator - */ - public async genManifest( - matScanModel: IMaterialScanModel, - matParsedModel: IMaterialParsedModel, - ): Promise<{ - manifestFilePath: string; // manifest 文件路径 - manifestObj: IComponentMaterial; // manifest 文件对象 - }> { - const manifestObj: Partial = { - // componentName: matParsedModel.defaultExportName, - title: matScanModel.pkgName, - docUrl: '', - screenshot: '', - npm: { - package: matScanModel.pkgName, - version: matScanModel.pkgVersion, - exportName: '', // matParsedModel.defaultExportName, - main: matScanModel.mainEntry, - destructuring: false, - subName: '', - }, - }; - - const defaultManifestFilePath = join( - dirname(matParsedModel.filePath), - './manifest.json', - ); - - // 填充 props - manifestObj.props = matParsedModel.props; - // 执行扩展点 - const manifest: any = await this.executeExtensionPoint( - 'mat:config:manifest', - { - manifestObj, - manifestFilePath: defaultManifestFilePath, - }, - ); - return { - manifestObj: manifest.manifestObj, - manifestFilePath: manifest.manifestFilePath, - }; - } - - /** - * 填充 props - * - * @public - * @param {IMaterialParsedModel} matParsedModel - * @returns {IMaterialinProp[]} - * @memberof BaseGenerator - */ - public populateProps( - matParsedModel: IMaterialParsedModel, - ): PropsSection['props'] { - // @ts-ignore - return matParsedModel.props; - } - - /** - * 执行扩展点 - * @param {string} extName 扩展点名称 - * @param {...any[]} args 参数 - * @returns {Promise} - * @memberof BaseGenerator - */ - public async executeExtensionPoint( - extName: string, - ...args: any[] - ): Promise { - const options = this.options; - const optionsExtensions: any = options.extensions; - const defaultExtensions: any = defaultExtension; - - const ext: any = - optionsExtensions && optionsExtensions[extName] - ? optionsExtensions[extName] - : defaultExtensions[extName]; - if (!ext) { - throw new Error(`Unsupported extension point: ${extName}`); - } - return ext(...args); - } -} - -export default Generator; diff --git a/packages/material-parser/src/index.ts b/packages/material-parser/src/index.ts index d38da4098..267a83629 100644 --- a/packages/material-parser/src/index.ts +++ b/packages/material-parser/src/index.ts @@ -1,8 +1,26 @@ -import Materialize from './Materialize'; - export { default as validate } from './validate'; export { default as schema } from './validate/schema.json'; export * from './types'; -export default Materialize; +import { IMaterializeOptions } from './types'; +import { ComponentMeta } from './otter-core'; +import scan from './scan'; +import generate from './generate'; +import parse from './parse'; +import localize from './localize'; + +export default async function(options: IMaterializeOptions): Promise { + const { accesser = 'local' } = options; + if (accesser === 'online') { + const entry = await localize(options); + options.entry = entry; + } + const scanedModel = await scan(options); + const parsedModel = await parse({ + filePath: scanedModel.entryFilePath, + fileContent: scanedModel.entryFileContent, + }); + const result = await generate(scanedModel, parsedModel); + return result; +} diff --git a/packages/material-parser/src/localize.ts b/packages/material-parser/src/localize.ts new file mode 100644 index 000000000..fb59a795d --- /dev/null +++ b/packages/material-parser/src/localize.ts @@ -0,0 +1,100 @@ +import spawn from 'cross-spawn-promise'; +import { ensureDir, ensureFile, writeFile } from 'fs-extra'; +import { join } from 'path'; +import semver from 'semver'; +import uuid from 'short-uuid'; +import { debug, OtterError } from './otter-core'; +import { IMaterializeOptions } from './types'; + +const log = debug.extend('mat'); + +/** + * 创建组件包 + * + * @private + * @param {{ + * pkgName: string; + * pkgVersion: string; + * }} params + * @returns {Promise} + * @memberof OnlineAccesser + */ +export async function createFakePackage(params: { + tempDir: string; + pkgName: string; + pkgVersion: string; + npmClient?: string; +}): Promise { + // 创建临时组件包 + const tempDir = params.tempDir; + const pkgJsonFilePath = join(tempDir, 'package.json'); + await ensureFile(pkgJsonFilePath); + await writeFile( + pkgJsonFilePath, + JSON.stringify({ + name: params.pkgName, + version: params.pkgVersion || '0.0.0', + dependencies: { + [params.pkgName]: params.pkgVersion || 'latest', + }, + }), + ); + + // 安装依赖 + const npmClient = params.npmClient || 'tnpm'; + await spawn(npmClient, ['i'], { stdio: 'inherit', cwd: tempDir } as any); +} + +/** + * 创建临时目录 + * + * @private + * @returns {Promise} 返回临时文件夹路径 + * @memberof LocalGenerator + */ +export async function createTempDir(): Promise { + const tempDirName = uuid.generate(); + const tempDir = join(__dirname, '../../node_modules/.temp/', tempDirName); + await ensureDir(tempDir); + log('create temp dir successfully', tempDir); + return tempDir; +} + +/** + * 分离物料组件名称和版本号 + * + * @private + * @param {string} pkgNameWithVersion + * @returns {{ [key: string]: any }} + * @memberof OnlineAccesser + */ +export function getPkgNameAndVersion(pkgNameWithVersion: string): { [key: string]: any } { + const matches = pkgNameWithVersion.match(/(@\d+\.\d+\.\d+)$/); + if (!matches) { + return { + name: pkgNameWithVersion, + }; + } + const semverObj = semver.coerce(matches[0]); + const name = pkgNameWithVersion.replace(matches[0], ''); + return { + version: semverObj && semverObj.version, + name, + }; +} + +// 将问题转化为本地物料化场景 +export default async function localize(options: IMaterializeOptions): Promise { + // 创建临时目录 + const tempDir = await createTempDir(); + // 创建组件包 + const { name, version } = getPkgNameAndVersion(options.entry); + await createFakePackage({ + pkgName: name, + pkgVersion: version, + tempDir, + npmClient: options.npmClient, + }); + + return join(tempDir, 'node_modules', name); +} diff --git a/packages/material-parser/src/otter-core/schema/types.ts b/packages/material-parser/src/otter-core/schema/types.ts index 5fe064355..97a6d5f52 100644 --- a/packages/material-parser/src/otter-core/schema/types.ts +++ b/packages/material-parser/src/otter-core/schema/types.ts @@ -8,7 +8,7 @@ /** * json schema for low code component protocol */ -export type IComponentMaterial = BasicSection & PropsSection & ConfigureSection; +export type ComponentMeta = BasicSection & PropsSection & ConfigureSection; export type PropType = BasicType | RequiredType | ComplexType; export type BasicType = "array" | "bool" | "func" | "number" | "object" | "string" | "node" | "element" | "any"; export type ComplexType = OneOf | OneOfType | ArrayOf | ObjectOf | Shape | Exact; diff --git a/packages/material-parser/src/parse/handlers/defaultPropsHandler.ts b/packages/material-parser/src/parse/handlers/defaultPropsHandler.ts new file mode 100644 index 000000000..74782c162 --- /dev/null +++ b/packages/material-parser/src/parse/handlers/defaultPropsHandler.ts @@ -0,0 +1,150 @@ +const { namedTypes: t, NodePath } = require('ast-types'); +type NodePathType = typeof NodePath; +const { + getPropertyName, + isReactComponentClass, + getMemberValuePath, + isReactForwardRefCall, + printValue, + resolveExportDeclaration, + resolveToValue, +} = require('react-docgen').utils; +const resolveFunctionDefinitionToReturnValue = require('react-docgen/dist/utils/resolveFunctionDefinitionToReturnValue'); + +function getDefaultValue(path: NodePathType) { + let node = path.node; + let defaultValue; + if (t.Literal.check(node)) { + defaultValue = node.raw; + } else { + if (t.AssignmentPattern.check(path.node)) { + path = resolveToValue(path.get('right')); + } else { + path = resolveToValue(path); + } + if (t.ImportDeclaration.check(path.node)) { + defaultValue = node.name; + } else { + node = path.node; + try { + defaultValue = printValue(path); + } catch (e) {} + } + } + if (typeof defaultValue !== 'undefined') { + return { + value: defaultValue, + computed: + t.CallExpression.check(node) || + t.MemberExpression.check(node) || + t.Identifier.check(node), + }; + } + + return null; +} + +function getStatelessPropsPath(componentDefinition: any) { + const value = resolveToValue(componentDefinition); + if (isReactForwardRefCall(value)) { + const inner = resolveToValue(value.get('arguments', 0)); + return inner.get('params', 0); + } + return value.get('params', 0); +} + +function getDefaultPropsPath(componentDefinition: any) { + let defaultPropsPath = getMemberValuePath( + componentDefinition, + 'defaultProps', + ); + if (!defaultPropsPath) { + return null; + } + + defaultPropsPath = resolveToValue(defaultPropsPath); + if (!defaultPropsPath) { + return null; + } + + if (t.FunctionExpression.check(defaultPropsPath.node)) { + // Find the value that is returned from the function and process it if it is + // an object literal. + const returnValue = resolveFunctionDefinitionToReturnValue( + defaultPropsPath, + ); + if (returnValue && t.ObjectExpression.check(returnValue.node)) { + defaultPropsPath = returnValue; + } + } + return defaultPropsPath; +} + +function getDefaultValuesFromProps( + properties: any[], + documentation: any, + isStateless: boolean, +) { + properties + // Don't evaluate property if component is functional and the node is not an AssignmentPattern + .filter( + propertyPath => + !isStateless || + t.AssignmentPattern.check(propertyPath.get('value').node), + ) + .forEach(propertyPath => { + if (t.Property.check(propertyPath.node)) { + const propName = getPropertyName(propertyPath); + if (!propName) return; + + const propDescriptor = documentation.getPropDescriptor(propName); + const defaultValue = getDefaultValue( + isStateless + ? propertyPath.get('value', 'right') + : propertyPath.get('value'), + ); + if (defaultValue) { + propDescriptor.defaultValue = defaultValue; + } + } else if (t.SpreadElement.check(propertyPath.node)) { + const resolvedValuePath = resolveToValue(propertyPath.get('argument')); + if (t.ObjectExpression.check(resolvedValuePath.node)) { + getDefaultValuesFromProps( + resolvedValuePath.get('properties'), + documentation, + isStateless, + ); + } + } + }); +} + +export default function defaultPropsHandler( + documentation: any, + componentDefinition: any, +) { + let statelessProps = null; + const defaultPropsPath = getDefaultPropsPath(componentDefinition); + /** + * function, lazy, memo, forwardRef etc components can resolve default props as well + */ + if (!isReactComponentClass(componentDefinition)) { + statelessProps = getStatelessPropsPath(componentDefinition); + } + + // Do both statelessProps and defaultProps if both are available so defaultProps can override + if (statelessProps && t.ObjectPattern.check(statelessProps.node)) { + getDefaultValuesFromProps( + statelessProps.get('properties'), + documentation, + true, + ); + } + if (defaultPropsPath && t.ObjectExpression.check(defaultPropsPath.node)) { + getDefaultValuesFromProps( + defaultPropsPath.get('properties'), + documentation, + false, + ); + } +} diff --git a/packages/material-parser/src/parser/handlers/index.ts b/packages/material-parser/src/parse/handlers/index.ts similarity index 86% rename from packages/material-parser/src/parser/handlers/index.ts rename to packages/material-parser/src/parse/handlers/index.ts index a4ff85a6c..d7fae8bdd 100644 --- a/packages/material-parser/src/parser/handlers/index.ts +++ b/packages/material-parser/src/parse/handlers/index.ts @@ -1,9 +1,11 @@ -const { handlers } = require('react-docgen'); import { propTypeHandler, contextTypeHandler, childContextTypeHandler, } from './propTypeHandler'; +import defaultPropsHandler from './defaultPropsHandler'; + +const { handlers } = require('react-docgen'); const defaultHandlers = [ propTypeHandler, @@ -12,7 +14,7 @@ const defaultHandlers = [ handlers.propTypeCompositionHandler, handlers.propDocBlockHandler, handlers.flowTypeHandler, - handlers.defaultPropsHandler, + defaultPropsHandler, handlers.componentDocblockHandler, handlers.displayNameHandler, handlers.componentMethodsHandler, diff --git a/packages/material-parser/src/parser/handlers/propTypeHandler.ts b/packages/material-parser/src/parse/handlers/propTypeHandler.ts similarity index 96% rename from packages/material-parser/src/parser/handlers/propTypeHandler.ts rename to packages/material-parser/src/parse/handlers/propTypeHandler.ts index 619a88eb4..74fb883b2 100644 --- a/packages/material-parser/src/parser/handlers/propTypeHandler.ts +++ b/packages/material-parser/src/parse/handlers/propTypeHandler.ts @@ -8,7 +8,7 @@ */ import { namedTypes as t, visit } from 'ast-types'; -import { throwStatement } from '@babel/types'; + const { resolveToValue, getPropType, @@ -29,7 +29,6 @@ function getRoot(node: any) { } return root; } -// import type Documentation from '../Documentation'; function isPropTypesExpression(path: any) { const moduleName = resolveToModule(path); @@ -81,11 +80,12 @@ function amendPropTypes(getDescriptor: any, path: any) { function getDefinePropertyValuePath(nodePath: any, propName: string) { const program = getRoot(nodePath); - const componentName = nodePath.node.id.name; let resultPath = nodePath; + if (!nodePath.node.id) return; + const componentName = nodePath.node.id.name; visit(program, { - visitCallExpression: function(path) { + visitCallExpression(path) { const args = path.get('arguments'); const argsNodeList = args.value; if ( diff --git a/packages/material-parser/src/parse/index.ts b/packages/material-parser/src/parse/index.ts new file mode 100644 index 000000000..028200af2 --- /dev/null +++ b/packages/material-parser/src/parse/index.ts @@ -0,0 +1,36 @@ +const reactDocs = require('react-docgen'); +import { transformItem } from './transform'; +import { debug } from '../otter-core'; +import { IMaterialParsedModel, IMaterialScanModel } from '../types'; +import resolver from './resolver'; +import handlers from './handlers'; + +export default function parse(params: { fileContent: string; filePath: string }): Promise { + const { fileContent, filePath } = params; + const result = reactDocs.parse( + fileContent, + (ast: any) => { + ast.__path = filePath; + return resolver(ast); + }, + handlers, + ); + const coms = result.reduce((res: any[], info: any) => { + if (!info || !info.props) return res; + const props = Object.keys(info.props).reduce((acc: any[], name) => { + try { + const item: any = transformItem(name, info.props[name]); + acc.push(item); + } catch (e) { + } finally { + return acc; + } + }, []); + res.push({ + componentName: info.displayName, + props, + }); + return res; + }, []); + return coms; +} diff --git a/packages/material-parser/src/parser/resolver/checkIsIIFE.ts b/packages/material-parser/src/parse/resolver/checkIsIIFE.ts similarity index 100% rename from packages/material-parser/src/parser/resolver/checkIsIIFE.ts rename to packages/material-parser/src/parse/resolver/checkIsIIFE.ts diff --git a/packages/material-parser/src/parser/resolver/index.ts b/packages/material-parser/src/parse/resolver/index.ts similarity index 73% rename from packages/material-parser/src/parser/resolver/index.ts rename to packages/material-parser/src/parse/resolver/index.ts index b16a99fd4..f0d67adbb 100644 --- a/packages/material-parser/src/parser/resolver/index.ts +++ b/packages/material-parser/src/parse/resolver/index.ts @@ -7,6 +7,12 @@ */ import { namedTypes as t, visit } from 'ast-types'; +import checkIsIIFE from './checkIsIIFE'; +import resolveHOC from './resolveHOC'; +import resolveIIFE from './resolveIIFE'; +import resolveImport from './resolveImport'; +import resolveTranspiledClass from './resolveTranspiledClass'; + const { isExportsOrModuleAssignment, isReactComponentClass, @@ -17,13 +23,6 @@ const { resolveExportDeclaration, resolveToValue, } = require('react-docgen').utils; -import checkIsIIFE from './checkIsIIFE'; -import resolveHOC from './resolveHOC'; -import resolveIIFE from './resolveIIFE'; -import resolveTranspiledClass from './resolveTranspiledClass'; - -const ERROR_MULTIPLE_DEFINITIONS = - 'Multiple exported component definitions found.'; function ignore() { return false; @@ -57,7 +56,7 @@ function resolveDefinition(definition: any) { return null; } -function getDefinition(definition: any) { +function getDefinition(definition: any): any { if (checkIsIIFE(definition)) { definition = resolveToValue(resolveIIFE(definition)); if (!isComponentDefinition(definition)) { @@ -70,14 +69,17 @@ function getDefinition(definition: any) { if (!isComponentDefinition(definition)) { definition = resolveTranspiledClass(definition); } + } else if (t.SequenceExpression.check(definition.node)) { + return getDefinition( + resolveToValue(definition.get('expressions').get(0)), + ); + } else { + definition = resolveImport( + definition, + findAllExportedComponentDefinition, + ); } } - // definition = resolveToValue(resolveIIFE(definition)); - // if (!isComponentDefinition(definition)) { - // definition = resolveTranspiledClass(definition); - // } - // definition = resolveToValue(resolveHOC(definition)); - return definition; } /** @@ -95,33 +97,37 @@ function getDefinition(definition: any) { * export default Definition; * export var Definition = ...; */ -export default function findExportedComponentDefinition(ast: any) { - let foundDefinition: any; +export default function findAllExportedComponentDefinition(ast: any) { + const components: any[] = []; function exportDeclaration(path: any) { - const definitions = resolveExportDeclaration(path).reduce( - (acc: any, definition: any) => { + const definitions = resolveExportDeclaration(path) + .reduce((acc: any[], definition: any) => { if (isComponentDefinition(definition)) { acc.push(definition); } else { definition = getDefinition(definition); - if (isComponentDefinition(definition)) { - acc.push(definition); + if (!Array.isArray(definition)) { + definition = [definition]; } + definition.forEach((def: any) => { + if (isComponentDefinition(def)) { + acc.push(def); + } + }); } return acc; - }, - [], - ); + }, []) + .map((definition: any) => resolveDefinition(definition)); if (definitions.length === 0) { return false; } - if (definitions.length > 1 || foundDefinition) { - // If a file exports multiple components, ... complain! - throw new Error(ERROR_MULTIPLE_DEFINITIONS); - } - foundDefinition = resolveDefinition(definitions[0]); + definitions.forEach((definition: any) => { + if (definition && components.indexOf(definition) === -1) { + components.push(definition); + } + }); return false; } @@ -142,6 +148,12 @@ export default function findExportedComponentDefinition(ast: any) { visitExportNamedDeclaration: exportDeclaration, visitExportDefaultDeclaration: exportDeclaration, + visitExportAllDeclaration: function(path) { + components.push( + ...resolveImport(path, findAllExportedComponentDefinition), + ); + return false; + }, visitAssignmentExpression(path: any) { // Ignore anything that is not `exports.X = ...;` or @@ -155,14 +167,14 @@ export default function findExportedComponentDefinition(ast: any) { if (!isComponentDefinition(path)) { path = getDefinition(path); } - if (foundDefinition) { - // If a file exports multiple components, ... complain! - throw new Error(ERROR_MULTIPLE_DEFINITIONS); + + const definition = resolveDefinition(path); + if (definition && components.indexOf(definition) === -1) { + components.push(definition); } - foundDefinition = resolveDefinition(path); return false; }, }); - return foundDefinition; + return components; } diff --git a/packages/material-parser/src/parser/resolver/resolveHOC.ts b/packages/material-parser/src/parse/resolver/resolveHOC.ts similarity index 99% rename from packages/material-parser/src/parser/resolver/resolveHOC.ts rename to packages/material-parser/src/parse/resolver/resolveHOC.ts index a5538a91f..426dc1e84 100644 --- a/packages/material-parser/src/parser/resolver/resolveHOC.ts +++ b/packages/material-parser/src/parse/resolver/resolveHOC.ts @@ -6,6 +6,7 @@ */ import { namedTypes as t, visit } from 'ast-types'; + const { isReactCreateClassCall, isReactForwardRefCall, diff --git a/packages/material-parser/src/parser/resolver/resolveIIFE.ts b/packages/material-parser/src/parse/resolver/resolveIIFE.ts similarity index 62% rename from packages/material-parser/src/parser/resolver/resolveIIFE.ts rename to packages/material-parser/src/parse/resolver/resolveIIFE.ts index 011328600..371cde5fb 100644 --- a/packages/material-parser/src/parser/resolver/resolveIIFE.ts +++ b/packages/material-parser/src/parse/resolver/resolveIIFE.ts @@ -1,20 +1,7 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ +import checkIsIIFE from './checkIsIIFE'; -// const { namedTypes: t, visit } = require("ast-types"); const resolveFunctionDefinitionToReturnValue = require('react-docgen/dist/utils/resolveFunctionDefinitionToReturnValue') .default; -// isReactCreateClassCall, -// isReactForwardRefCall, -// resolveToValue, -// resolveHOC - -import checkIsIIFE from './checkIsIIFE'; /** * If the path is a call expression, it recursively resolves to the * rightmost argument, stopping if it finds a React.createClass call expression diff --git a/packages/material-parser/src/parse/resolver/resolveImport.ts b/packages/material-parser/src/parse/resolver/resolveImport.ts new file mode 100644 index 000000000..86c87f279 --- /dev/null +++ b/packages/material-parser/src/parse/resolver/resolveImport.ts @@ -0,0 +1,60 @@ +import { namedTypes as t } from 'ast-types'; +import fs from 'fs'; +import p from 'path'; + +function getRoot(node: any) { + let root = node.parent; + while (root.parent) { + root = root.parent; + } + return root.node; +} + +function isImportLike(node: any) { + return ( + t.ImportDeclaration.check(node) || + t.ExportAllDeclaration.check(node) || + t.ExportNamedDeclaration.check(node) + ); +} + +function getPath(path: any, name: any) { + const root = getRoot(path); + if (!root) return; + let { __path } = root; + __path = p.dirname(__path); + // is directory + if (fs.existsSync(p.resolve(__path, name))) { + name = name + '/index'; + } + const suffix = suffixes.find(suf => { + return fs.existsSync(p.resolve(__path, name + suf)); + }); + if (!suffix) return; + return p.resolve(__path, name + suffix); +} + +const buildParser = require('react-docgen/dist/babelParser').default; +const parser = buildParser(); +const suffixes = ['.js', '.jsx', '.ts', '.tsx']; + +export default function resolveImport(path: any, callback: any) { + let name; + if (path.name === 'local') { + name = path.parentPath.parentPath.parentPath.node.source.value; + } else if (!isImportLike(path.node)) { + return path; + } else { + name = path.node.source.value; + } + if (name) { + const __path = getPath(path, name); + if (!__path) return path; + const fileContent = fs.readFileSync(__path, 'utf8'); + const ast = parser.parse(fileContent); + ast.__src = fileContent; + ast.__path = __path; + return callback(ast); + } + return path; +} diff --git a/packages/material-parser/src/parser/resolver/resolveTranspiledClass.ts b/packages/material-parser/src/parse/resolver/resolveTranspiledClass.ts similarity index 80% rename from packages/material-parser/src/parser/resolver/resolveTranspiledClass.ts rename to packages/material-parser/src/parse/resolver/resolveTranspiledClass.ts index c4bfc6e72..0357966ab 100644 --- a/packages/material-parser/src/parser/resolver/resolveTranspiledClass.ts +++ b/packages/material-parser/src/parse/resolver/resolveTranspiledClass.ts @@ -1,11 +1,3 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - import { builders, namedTypes as t, NodePath, visit } from 'ast-types'; /** * If the path is a call expression, it recursively resolves to the @@ -19,7 +11,7 @@ export default function resolveTranspiledClass(path: any) { visitFunctionDeclaration(arg) { classPath = new NodePath( builders.functionDeclaration( - arg.node.id, + arg.node.id || 'Default', [], builders.blockStatement([ builders.returnStatement( diff --git a/packages/material-parser/src/parse/transform.ts b/packages/material-parser/src/parse/transform.ts new file mode 100644 index 000000000..11de2aa22 --- /dev/null +++ b/packages/material-parser/src/parse/transform.ts @@ -0,0 +1,132 @@ +export function transformType(type: any) { + if (typeof type === 'string') return type; + const { name, elements, value = elements, computed, required } = type; + if (!value && !required) { + return name; + } + if (computed !== undefined && value) { + return eval(value); + } + const result: any = { + type: name, + }; + if (required) { + result.isRequired = required; + } + switch (name) { + case 'number': + case 'string': + case 'bool': + case 'any': + case 'func': + case 'symbol': + case 'object': + break; + case 'literal': + return eval(value); + case 'enum': + case 'tuple': + result.type = 'oneOf'; + result.value = value.map(transformType); + break; + case 'union': + result.type = 'oneOfType'; + result.value = value.map(transformType); + break; + case 'boolean': + result.type = 'bool'; + break; + case 'Array': { + result.type = 'arrayOf'; + const v = transformType(value[0]); + if (typeof v.type === 'string') result.value = v.type; + break; + } + case 'signature': { + result.type = 'shape'; + const { + signature: { properties }, + } = type; + if (properties.length === 0) { + result.type = 'object'; + } else if (properties.length === 1 && typeof properties[0].key === 'object') { + result.type = 'objectOf'; + const v = transformType(properties[0].value); + if (typeof v.type === 'string') result.value = v.type; + } else { + result.value = properties + .filter((item: any) => typeof item.key !== 'object') + .map((prop: any) => { + const { + key, + value: { name, ...others }, + } = prop; + return transformItem(key, { + ...others, + type: { + name, + }, + }); + }); + } + break; + } + case 'objectOf': + case 'arrayOf': + case 'instanceOf': + result.value = transformType(value); + break; + case 'exact': + case 'shape': + result.value = Object.keys(value).map((n) => { + // tslint:disable-next-line:variable-name + const { name: _name, ...others } = value[n]; + return transformItem(n, { + ...others, + type: { + name: _name, + }, + }); + }); + break; + case (name.match('ReactNode$') || {}).input: + result.type = 'node'; + break; + case (name.match('Element$') || {}).input: + result.type = 'element'; + break; + case (name.match('ElementType$') || {}).input: + result.type = 'elementType'; + break; + default: + result.type = 'instanceOf'; + result.value = name; + break; + } + return result; +} + +export function transformItem(name: string, item: any) { + const { description, flowType, type = flowType, required, defaultValue } = item; + const result: any = { + name, + propType: transformType({ + ...type, + required: !!required, + }), + }; + if (description) { + result.description = description; + } + if (defaultValue) { + try { + const value = eval(defaultValue.value); + result.defaultValue = value; + } catch (e) {} + } + if (result.propType === undefined) { + delete result.propType; + } + + return result; +} diff --git a/packages/material-parser/src/parser/BaseParser.ts b/packages/material-parser/src/parser/BaseParser.ts deleted file mode 100644 index baf292875..000000000 --- a/packages/material-parser/src/parser/BaseParser.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { OtterError } from '../otter-core'; -import { - EcologyType, - IMaterializeOptions, - IMaterialParsedModel, - IMaterialScanModel, - IParser, - SourceType, -} from '../types'; - -/** - * 解析器基类 - * @abstract - * @class BaseParser - * @implements {IParser} - */ -abstract class BaseParser implements IParser { - /** - * 识别语法生态,判断是 react、vue、rax - * @static - * @param {IMaterializeOptions} options - * @returns {Promise} - * @memberof BaseParser - */ - public static recognizeEcology( - options: IMaterializeOptions, - ): Promise { - // TODO 识别物料组件生态 - return Promise.resolve(EcologyType.REACT); - } - - private options: IMaterializeOptions; - - constructor(options: IMaterializeOptions) { - this.options = options; - } - - public async parse( - model: IMaterialScanModel, - ): Promise { - const results: IMaterialParsedModel[] = []; - switch (model.sourceType) { - case SourceType.MODULE: { - for (const item of model.modules) { - const parsedModel: IMaterialParsedModel = await this.parseES6({ - model, - filePath: item.filePath, - fileContent: item.fileContent, - }); - results.push(parsedModel); - } - break; - } - case SourceType.MAIN: { - this.parseES5(model); - break; - } - default: { - throw new OtterError(`Unsupported SourceType [${model.sourceType}]`); - } - } - return results; - } - - public abstract parseES5( - model: IMaterialScanModel, - ): Promise; - - public abstract parseES6(params: { - model: IMaterialScanModel; - filePath: string; - fileContent: string; - }): Promise; -} - -export default BaseParser; diff --git a/packages/material-parser/src/parser/ReactParser.ts b/packages/material-parser/src/parser/ReactParser.ts deleted file mode 100644 index 5ececea15..000000000 --- a/packages/material-parser/src/parser/ReactParser.ts +++ /dev/null @@ -1,704 +0,0 @@ -import { CodeGenerator } from '@babel/generator'; -// import { parse } from '@babel/parser'; -const buildParser = require('react-docgen/dist/babelParser').default; -const reactDocs = require('react-docgen'); -import traverse from '@babel/traverse'; -import * as t from '@babel/types'; -const { utils: ReactDocUtils } = require('react-docgen'); -import { debug } from '../otter-core'; -import { - IMaterialParsedModel, - IMaterialScanModel, - IPropType, - IPropTypes, - SourceType, -} from '../types'; -import BaseParser from './BaseParser'; -import resolver from './resolver'; -import handlers from './handlers'; - -const log = debug.extend('mat'); -const parser = buildParser(); - -function transformType(type: any): any { - const { name, value, computed, required } = type; - if (!value && !required) { - return name; - } - if (computed !== undefined && value) { - // tslint:disable-next-line:no-eval - return eval(value); - } - const result: any = { - type: name, - }; - if (required) { - result.isRequired = required; - } - if (Array.isArray(value)) { - if (name === 'enum') { - result.type = 'oneOf'; - } else if (name === 'union') { - result.type = 'oneOfType'; - } - result.value = value.map(transformType); - } else if (typeof value === 'object') { - if (name === 'objectOf' || name === 'arrayOf' || name === 'instanceOf') { - result.value = transformType(value); - } else { - result.value = Object.keys(value).map((n: string) => { - // tslint:disable-next-line:variable-name - const { name: _name, ...others } = value[n]; - return transformItem(n, { - ...others, - type: { - name: _name, - }, - }); - }); - } - } else if (value !== undefined) { - result.value = value; - } - return result; -} - -function transformItem(name: string, item: any): any { - const { description, type, required, defaultValue } = item; - const result: any = { - name, - propType: transformType({ - ...type, - required: !!required, - }), - }; - if (description) { - result.description = description; - } - if (defaultValue) { - try { - const value = eval(defaultValue.value); - result.defaultValue = value; - } catch (e) {} - } - - return result; -} -/** - * 解析 react 生态下的组件 - * - * @class ReactParser - * @extends {BaseParser} - */ -class ReactParser extends BaseParser { - /** - * 解析 ExportStatement - * @static - * @returns {Promise} - * @memberof ReactParser - */ - public static async parseExportedStatement( - fileContent: string, - sourceType: string, - ): Promise< - Array<{ - localName: string; - exportedName: string; - source: string; - }> - > { - const ast = parser.parse(fileContent); - - // @ts-ignore - ast.__src = fileContent; - - const specifiers: any = []; - - // 组装 localName 和 exportedName - traverse(ast, { - enter(path) { - if (t.isExportNamedDeclaration(path.node)) { - path.node.specifiers.forEach(spec => { - if (t.isExportSpecifier(spec)) { - const source = (path.node as t.ExportNamedDeclaration).source; - specifiers.push({ - localName: spec.local.name, - exportedName: spec.exported.name, - source: t.isLiteral(source) ? (source as any).value : '', - }); - } - }); - } - }, - }); - // 组装 source - traverse(ast, { - enter(path) { - if (t.isImportDeclaration(path.node)) { - const source = path.node.source; - path.node.specifiers.forEach(spec => { - if (t.isImportDefaultSpecifier(spec)) { - const target = specifiers.find( - (inner: any) => inner.localName === spec.local.name, - ); - if (target) { - target.source = source.value; - } - } - }); - } - }, - }); - debug('specifiers', specifiers); - return specifiers; - } - public static parseProperties(objectPath: any): IPropTypes { - const results: IPropTypes = objectPath - .get('properties') - .map((p: any) => - transformItem( - p.get('key').node.name, - ReactDocUtils.getPropType(p.get('value')), - ), - ); - - return results; - } - - public async parseES5( - model: IMaterialScanModel, - ): Promise { - const parsedModel: IMaterialParsedModel = { - filePath: '', - defaultExportName: '', - componentNames: [], - importModules: [], - exportModules: [], - subModules: [], - propsTypes: [], - propsDefaults: [], - }; - - const mainEntryItem: any = model.modules.find( - item => item.filePath === model.mainEntry, - ); - - const result = reactDocs.parse( - mainEntryItem.fileContent, - resolver, - handlers, - ); - const props = Object.keys(result.props || {}).map(name => { - return transformItem(name, result.props[name]); - }); - - return { - filePath: mainEntryItem.filePath, - // defaultExportName, - // subModules, - // propsTypes, - props, - } as any; - - // log('mainEntryItem', mainEntryItem); - // const ast = parser.parse(mainEntryItem.file); - - // @ts-ignore - // ast.__src = mainEntryItem.file; - - // // 获取 defaultExportName - // traverse(ast, { - // enter(path) { - // if (t.isExpressionStatement(path.node)) { - // if ( - // t.isAssignmentExpression(path.node.expression) && - // t.isMemberExpression(path.node.expression.left) && - // t.isIdentifier(path.node.expression.left.object) && - // t.isIdentifier(path.node.expression.right) && - // path.node.expression.left.object.name === 'exports' && - // (path.node.expression.left.property.name === 'default' || - // path.node.expression.left.property.value === 'default') - // ) { - // // 支持 export default Demo 写法 - // const tempVarName = path.node.expression.right.name; - // let defaultExportName = ''; - // traverse(ast, { - // enter(innerPath) { - // if ( - // t.isVariableDeclaration(innerPath.node) && - // Array.isArray(innerPath.node.declarations) && - // innerPath.node.declarations.length && - // t.isVariableDeclarator(innerPath.node.declarations[0]) && - // t.isIdentifier(innerPath.node.declarations[0].id) && - // innerPath.node.declarations[0].id.name === tempVarName && - // t.isIdentifier(innerPath.node.declarations[0].init) - // ) { - // defaultExportName = innerPath.node.declarations[0].init.name; - // } - // }, - // }); - // parsedModel.defaultExportName = defaultExportName; - // log('isIdentifier defaultExportName', defaultExportName); - // } - // } - // }, - // }); - - // traverse(ast, { - // enter(path) { - // // 获取 componentNames - // if (t.isVariableDeclaration(path.node)) { - // if ( - // t.isVariableDeclarator(path.node.declarations) && - // t.isIdentifier(path.node.declarations.init) && - // t.isIdentifier(path.node.declarations.id) - // ) { - // const exportedName = path.node.declarations.init.name; - // const localName = path.node.declarations.id.name; - // log('isIdentifier componentNames', exportedName); - // parsedModel.componentNames.push({ - // exportedName, - // localName, - // }); - // } - // } - // // 获取 exportModules - // if (t.isExpressionStatement(path.node)) { - // // 对应 export function DemoFunc() {} 或 export { DemoFunc } 写法 - // if ( - // t.isAssignmentExpression(path.node.expression) && - // t.isMemberExpression(path.node.expression.left) && - // t.isIdentifier(path.node.expression.left.object) && - // t.isIdentifier(path.node.expression.left.property) && - // t.isIdentifier(path.node.expression.right) && - // path.node.expression.left.object.name === 'exports' - // ) { - // const exportedName = path.node.expression.left.property.name; - // const localName = path.node.expression.right.name; - // parsedModel.exportModules.push({ - // exportedName: - // exportedName === 'default' - // ? parsedModel.defaultExportName - // : exportedName, - // localName: - // exportedName === 'default' - // ? parsedModel.defaultExportName - // : localName, - // }); - // } - // // 支持 export { default as DemoFunc } from './DemoFunc' 写法 - // if ( - // t.isCallExpression(path.node.expression) && - // t.isMemberExpression(path.node.expression.callee) && - // t.isIdentifier(path.node.expression.callee.object) && - // t.isIdentifier(path.node.expression.callee.property) && - // path.node.expression.callee.object.name === 'Object' && - // path.node.expression.callee.property.name === 'defineProperty' && - // Array.isArray(path.node.expression.arguments) && - // t.isIdentifier(path.node.expression.arguments[0]) && - // (path.node.expression.arguments[0] as t.Identifier).name === - // 'exports' && - // t.isLiteral(path.node.expression.arguments[1]) - // ) { - // // 对应 export function DemoFunc() {} 或 export { DemoFunc } 写法 - // const args = path.node.expression.arguments as any; - // const funcName = args[1].value; - // if (funcName !== '__esModule') { - // parsedModel.exportModules.push({ - // exportedName: funcName, - // localName: funcName, - // }); - // } - // } - // } - // // 获取 importModules - // if ( - // t.isVariableDeclaration(path.node) && - // Array.isArray(path.node.declarations) && - // path.node.declarations.length - // ) { - // path.node.declarations.forEach(dec => { - // // 支持 import Demo from './demo' 写法 - // if ( - // t.isVariableDeclarator(dec) && - // t.isIdentifier(dec.id) && - // t.isCallExpression(dec.init) && - // t.isIdentifier(dec.init.callee) && - // ['_interopRequireWildcard', '_interopRequireDefault'].includes( - // dec.init.callee.name, - // ) && - // // dec.init.callee.name === '_interopRequireWildcard' && - // Array.isArray(dec.init.arguments) && - // t.isCallExpression(dec.init.arguments[0]) && - // t.isIdentifier( - // (dec.init.arguments[0] as t.CallExpression).callee, - // ) && - // ((dec.init.arguments[0] as t.CallExpression) - // .callee as t.Identifier).name === 'require' - // ) { - // const localName = dec.id.name; - // const args = (dec.init.arguments[0] as t.CallExpression) - // .arguments as any; - // const source = args[0].value; - // parsedModel.importModules.push({ - // importDefaultName: localName, - // localName, - // source, - // }); - // } - - // // 支持 import { Demo as Demo2 } from './demo' 写法 - // if ( - // t.isVariableDeclarator(dec) && - // t.isIdentifier(dec.id) && - // t.isCallExpression(dec.init) && - // t.isIdentifier(dec.init.callee) && - // dec.init.callee.name === 'require' && - // Array.isArray(dec.init.arguments) && - // t.isLiteral(dec.init.arguments[0]) - // ) { - // const args = dec.init.arguments as any; - // const source = args[0].value; - // const importName = dec.id.name; - // const localName = dec.id.name; - // // 遍历查找出 importName 和 localName - // // ES5 本身并不支持按需加载,故 import 都是全量导入 - // // 但如果使用了诸如:babel-plugin-import 等插件,会自动更改编译之后的 ES5 代码 - // parsedModel.importModules.push({ - // importName, - // localName, - // source, - // }); - // } - // }); - // } - - // // 获取 subModules - // if ( - // t.isExpressionStatement(path.node) && - // t.isAssignmentExpression(path.node.expression) && - // t.isMemberExpression(path.node.expression.left) - // ) { - // if ( - // t.isIdentifier(path.node.expression.left.object) && - // path.node.expression.left.object.name === - // parsedModel.defaultExportName - // ) { - // // 支持 SFC.SubDemo1 = SubDemo1; 写法 - // if (t.isIdentifier(path.node.expression.right)) { - // parsedModel.subModules.push({ - // objectName: [path.node.expression.left.object.name], - // propertyName: path.node.expression.left.property.name, - // isValueAnonymousFunc: false, - // value: path.node.expression.right.name, - // }); - // } - - // // 支持 SFC.SubDemo2 = function() {}; 写法 - // if (t.isFunctionExpression(path.node.expression.right)) { - // const rightID = path.node.expression.right.id as any; - // parsedModel.subModules.push({ - // objectName: [path.node.expression.left.object.name], - // propertyName: path.node.expression.left.property.name, - // isValueAnonymousFunc: !rightID, - // value: rightID ? rightID.name : undefined, - // }); - // } - // } - - // if (t.isMemberExpression(path.node.expression.left.object)) { - // if (t.isIdentifier(path.node.expression.right)) { - // // 支持 DemoFunc4.Test.Obj2 = Obj3; 写法 - // const tempLeftObject = path.node.expression.left.object as any; - // parsedModel.subModules.push({ - // objectName: [ - // tempLeftObject.object.name, - // tempLeftObject.property.name, - // ], - // propertyName: path.node.expression.left.property.name, - // isValueAnonymousFunc: false, - // value: path.node.expression.right.name, - // }); - // } - // if (t.isFunctionExpression(path.node.expression.right)) { - // // 支持 DemoFunc4.Test.Obj2 = function() {}; 写法 - // const rightID = path.node.expression.right.id as any; - // const tempLeftObject = path.node.expression.left.object as any; - // parsedModel.subModules.push({ - // objectName: [ - // tempLeftObject.object.name, - // tempLeftObject.property.name, - // ], - // propertyName: path.node.expression.left.property.name, - // isValueAnonymousFunc: !rightID, - // value: rightID ? rightID.name : undefined, - // }); - // } - // } - // } - - // // 获取 propsTypes 和 defaultProps - // if ( - // t.isExpressionStatement(path.node) && - // t.isAssignmentExpression(path.node.expression) && - // t.isMemberExpression(path.node.expression.left) && - // t.isObjectExpression(path.node.expression.right) && - // t.isIdentifier(path.node.expression.left.object) && - // t.isIdentifier(path.node.expression.left.property) && - // path.node.expression.left.object.name === - // parsedModel.defaultExportName && - // ['propTypes', 'defaultProps'].includes( - // path.node.expression.left.property.name, - // ) - // ) { - // // 处理 propTypes - // if (path.node.expression.left.property.name === 'propTypes') { - // path.node.expression.right.properties.forEach(prop => { - // if (t.isProperty(prop)) { - // if (t.isMemberExpression(prop.value)) { - // if (t.isIdentifier(prop.value.object)) { - // // 支持 optionalArray: PropTypes.array 写法 - // parsedModel.propsTypes.push({ - // name: prop.key.name, - // type: prop.value.property.name, - // required: false, - // }); - // } - // if (t.isMemberExpression(prop.value.object)) { - // // 支持 optionalArray: PropTypes.array.isRequired 写法 - // parsedModel.propsTypes.push({ - // name: prop.key.name, - // type: prop.value.object.property.name, - // required: - // prop.value.object.property.name === 'isRequired', - // }); - // } - // if ( - // t.isCallExpression(prop.value.object) && - // t.isMemberExpression(prop.value.object.callee) - // ) { - // // 支持 optionalArray: PropTypes.shape().isRequired 写法 - // parsedModel.propsTypes.push({ - // name: prop.key.name, - // type: prop.value.object.callee.property.name, - // required: prop.value.property.name === 'isRequired', - // }); - // } - // } - // if ( - // t.isCallExpression(prop.value) && - // t.isMemberExpression(prop.value.callee) - // ) { - // // 支持 optionalArray: PropTypes.shape() 写法 - // parsedModel.propsTypes.push({ - // name: prop.key.name, - // type: prop.value.callee.property.name, - // required: false, - // }); - // } - // } - // }); - // } - // // 处理 defaultProps - // if (path.node.expression.left.property.name === 'defaultProps') { - // path.node.expression.right.properties.forEach(prop => { - // if (t.isProperty(prop)) { - // if (t.isObjectExpression(prop.value)) { - // const defaultValue = new CodeGenerator( - // t.objectExpression(prop.value.properties), - // ).generate().code; - // parsedModel.propsDefaults.push({ - // name: prop.key.name, - // defaultValue, - // }); - // } - // } - // }); - // } - // } - // }, - // }); - - // log('traverse done.'); - // log('parsedModel.defaultExportName', parsedModel.defaultExportName); - // log('parsedModel.componentNames', parsedModel.componentNames); - // log('parsedModel.importModules', parsedModel.importModules); - // log('parsedModel.exportModules', parsedModel.exportModules); - // log('parsedModel.subModules', parsedModel.subModules); - // log('parsedModel.propsTypes', parsedModel.propsTypes); - // log('parsedModel.propsDefaults', parsedModel.propsDefaults); - // log('parsedModel', parsedModel); - // return parsedModel; - } - - public async parseES6(params: { - model: IMaterialScanModel; - filePath: string; - fileContent: string; - }): Promise { - const ast = parser.parse(params.fileContent); - const result = reactDocs.parse(params.fileContent, resolver, handlers); - const props = Object.keys(result.props || {}).map(name => { - return transformItem(name, result.props[name]); - }); - const defaultExportName = await this.parseDefaultExportNameES6(ast); - // const subModules = await this.parseSubModulesES6(ast); - - return { - filePath: params.filePath, - defaultExportName, - // subModules, - props, - } as any; - } - - /** - * 解析 AST 获取 defaultExportName - * 支持的写法: - * - export default Demo - * - export default function Demo() {} - * - export default class Demo {} - * - * @private - * @param {*} ast - * @memberof ReactParser - */ - private async parseDefaultExportNameES6(ast: any): Promise { - let defaultExportName = ''; - traverse(ast, { - enter(path) { - // 获取 defaultExportName - if (t.isExportDefaultDeclaration(path.node)) { - if (t.isIdentifier(path.node.declaration)) { - // 支持 export default Demo 写法 - defaultExportName = path.node.declaration.name; - log('isIdentifier defaultExportName', defaultExportName); - } - if (t.isFunctionDeclaration(path.node.declaration)) { - if (t.isIdentifier(path.node.declaration.id)) { - // 支持 export default function Demo() {} 写法 - defaultExportName = path.node.declaration.id.name; - log('isFunctionDeclaration defaultExportName', defaultExportName); - } - } - if (t.isClassDeclaration(path.node.declaration)) { - if (t.isIdentifier(path.node.declaration.id)) { - // 支持 export default class Demo {} 写法 - defaultExportName = path.node.declaration.id.name; - log('isClassDeclaration defaultExportName', defaultExportName); - } - } - if (t.isCallExpression(path.node.declaration)) { - const traverseCallExp: any = (args: any[]) => { - const arg = args[0]; - if (t.isIdentifier(arg)) { - return arg.name; - } - return traverseCallExp(arg.arguments); - }; - defaultExportName = traverseCallExp( - path.node.declaration.arguments, - ); - } - } - }, - }); - return defaultExportName; - } - - /** - * 解析 AST 获取 subModules - * 支持的写法: - * - DemoFunc4.Test = Test; - * - DemoFunc4.Test = function() {}; - * - DemoFunc4.Test.Obj2 = Obj3; - * - DemoFunc4.Test.Obj2 = function() {}; - * - * @private - * @param {*} ast - * @returns {Promise>} - * @memberof ReactParser - */ - private async parseSubModulesES6( - ast: any, - ): Promise< - Array<{ - objectName: string[]; - propertyName: string; - value?: string; - isValueAnonymousFunc: boolean; - }> - > { - const results: any[] = []; - traverse(ast, { - enter(path) { - if (t.isExpressionStatement(path.node)) { - if (t.isAssignmentExpression(path.node.expression)) { - if (t.isMemberExpression(path.node.expression.left)) { - if (t.isIdentifier(path.node.expression.left.object)) { - if (t.isIdentifier(path.node.expression.right)) { - // 支持 DemoFunc4.Test = Test; 写法 - results.push({ - objectName: [path.node.expression.left.object.name], - propertyName: path.node.expression.left.property.name, - isValueAnonymousFunc: false, - value: path.node.expression.right.name, - }); - } - if (t.isFunctionExpression(path.node.expression.right)) { - // 支持 DemoFunc4.Test = function() {}; 写法 - const rightID = !path.node.expression.right.id as any; - results.push({ - objectName: [path.node.expression.left.object.name], - propertyName: path.node.expression.left.property.name, - isValueAnonymousFunc: !!rightID, - value: rightID ? rightID.name : undefined, - }); - } - } - if (t.isMemberExpression(path.node.expression.left.object)) { - if (t.isIdentifier(path.node.expression.right)) { - // 支持 DemoFunc4.Test.Obj2 = Obj3; 写法 - const tempLeftObject = path.node.expression.left - .object as any; - results.push({ - objectName: [ - tempLeftObject.object.name, - tempLeftObject.property.name, - ], - propertyName: path.node.expression.left.property.name, - isValueAnonymousFunc: false, - value: path.node.expression.right.name, - }); - } - if (t.isFunctionExpression(path.node.expression.right)) { - // 支持 DemoFunc4.Test.Obj2 = function() {}; 写法 - const rightID = !path.node.expression.right.id as any; - const tempLeftObject = path.node.expression.left - .object as any; - results.push({ - objectName: [ - tempLeftObject.object.name, - tempLeftObject.property.name, - ], - propertyName: path.node.expression.left.property.name, - isValueAnonymousFunc: !!rightID, - value: rightID ? rightID.name : undefined, - }); - } - } - } - } - } - }, - }); - return results; - } -} - -export default ReactParser; diff --git a/packages/material-parser/src/scan.ts b/packages/material-parser/src/scan.ts new file mode 100644 index 000000000..05d661d75 --- /dev/null +++ b/packages/material-parser/src/scan.ts @@ -0,0 +1,68 @@ +import { IMaterializeOptions, IMaterialScanModel, SourceType } from './types'; +import { pathExists, readFile, lstatSync } from 'fs-extra'; +import { join } from 'path'; +import { debug } from './otter-core'; +const log = debug.extend('mat'); + +export default async function scan(options: IMaterializeOptions): Promise { + const model: IMaterialScanModel = { + pkgName: '', + pkgVersion: '', + sourceType: SourceType.MODULE, + entryFilePath: '', + entryFileContent: '', + mainFilePath: '', + }; + log('options', options); + // 入口文件路径 + let entryFilePath = options.entry; + const stats = lstatSync(entryFilePath); + if (!stats.isFile()) { + let mainFilePath = ''; + const pkgJsonPath = join(entryFilePath, 'package.json'); + // 判断是否存在 package.json + if (!(await pathExists(pkgJsonPath))) { + throw new Error(`Cannot find package.json. ${pkgJsonPath}`); + } + // 读取 package.json + let pkgJson = await resolvePkgJson(pkgJsonPath); + model.pkgName = pkgJson.name; + model.pkgVersion = pkgJson.version; + if (pkgJson.module) { + // 支持 es module + model.sourceType = SourceType.MODULE; + mainFilePath = pkgJson.module; + } else if (pkgJson.main) { + // 支持 commonjs + model.sourceType = SourceType.MAIN; + mainFilePath = pkgJson.main; + } else { + mainFilePath = './index.js'; + } + model.mainFilePath = mainFilePath; + entryFilePath = join(entryFilePath, mainFilePath); + } + + log('entryFilePath', entryFilePath); + const entryFileContent = await loadFile(entryFilePath); + log('entryFile', entryFileContent); + model.entryFilePath = entryFilePath; + model.entryFileContent = entryFileContent; + // 记录入口文件 + log('model', model); + return model; +} + +export async function loadFile(filePath: string): Promise { + const content: string | Buffer = await readFile(filePath); + if (typeof content === 'string') { + return content; + } + return content.toString(); +} + +export async function resolvePkgJson(pkgJsonPath: string): Promise<{ [k: string]: any }> { + const content = await loadFile(pkgJsonPath); + const json = JSON.parse(content); + return json; +} diff --git a/packages/material-parser/src/scanner/Scanner.ts b/packages/material-parser/src/scanner/Scanner.ts deleted file mode 100644 index 880678e13..000000000 --- a/packages/material-parser/src/scanner/Scanner.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { pathExists, readFile, statSync } from 'fs-extra'; -import { dirname, join } from 'path'; -import { debug } from '../otter-core'; -import BaseParser from '../parser/BaseParser'; -import ReactParser from '../parser/ReactParser'; -import { - IMaterializeOptions, - IMaterialScanModel, - IScanner, - SourceType, -} from '../types'; - -const log = debug.extend('mat'); - -/** - * 文件扫描器 - * - * @class Scanner - * @implements {IScanner} - */ -class Scanner implements IScanner { - public options: IMaterializeOptions; - - constructor(options: IMaterializeOptions) { - this.options = options; - } - - public async scan(): Promise { - const model: IMaterialScanModel = { - pkgName: '', - pkgVersion: '', - mainEntry: '', - sourceType: SourceType.MODULE, - modules: [], - }; - const options = this.options; - log('options', options); - // 入口文件路径 - let entryFilePath = null; - const cwd = options.cwd ? options.cwd : ''; - const entry = options.entry; - const isDepsMode = cwd !== entry; - const pkgJsonPath = join(cwd, 'package.json'); - // 判断是否存在 package.json - // if (!(await pathExists(pkgJsonPath))) { - // throw new Error(`Cannot find package.json. ${pkgJsonPath}`); - // } - // 读取 package.json - let pkgJson = await this.resolvePkgJson(pkgJsonPath); - model.pkgName = pkgJson.name; - model.pkgVersion = pkgJson.version; - if (isDepsMode) { - pkgJson = await this.resolvePkgJson(join(entry, 'package.json')); - } - if (pkgJson.module) { - // 支持 es module - model.sourceType = SourceType.MODULE; - entryFilePath = pkgJson.module; - } else if (pkgJson.main) { - // 支持 commonjs - model.sourceType = SourceType.MAIN; - entryFilePath = pkgJson.main; - } else { - entryFilePath = './index.js'; - } - entryFilePath = join(isDepsMode ? entry : cwd, entryFilePath); - log('entryFilePath', entryFilePath); - const entryFile = await this.loadFile(entryFilePath); - log('entryFile', entryFile); - model.mainEntry = entryFilePath; - // 记录入口文件 - model.modules.push({ - filePath: entryFilePath, - fileContent: entryFile, - }); - log('model', model); - // debugger; - if (options.isExportedAsMultiple) { - // 解析 entryFile,提取出 export 语句 - const modules = await this.parseEntryFile({ - entryFile, - entryFilePath, - sourceType: model.sourceType, - }); - model.modules = [...modules]; - } - log('model', model); - return model; - } - - /** - * 判断是否为文件夹 - * @param {string} filePath 文件路径 - * @returns {Promise} - * @memberof LocalScanner - */ - public async isDirectory(filePath: string): Promise { - log('materialIn', 'isDirectory - filePath', filePath); - const stats = statSync(filePath); - return stats.isDirectory(); - } - - public async loadFile(filePath: string): Promise { - const content: string | Buffer = await readFile(filePath); - if (typeof content === 'string') { - return content; - } - return content.toString(); - } - - public async resolvePkgJson( - pkgJsonPath: string, - ): Promise<{ [k: string]: any }> { - const content = await this.loadFile(pkgJsonPath); - const json = JSON.parse(content); - return json; - } - - /** - * 解析入口文件,获取要导出的模块内容 - * @private - * @param {{ - * entryFile: string; - * entryFilePath: string; - * sourceType: string; - * }} params - * @returns {Promise} - * @memberof LocalScanner - */ - private async parseEntryFile(params: { - entryFile: string; - entryFilePath: string; - sourceType: string; - }): Promise { - const modules: any = []; - const entryFileDirName = dirname(params.entryFilePath); - const ecology = await BaseParser.recognizeEcology(this.options); - if (ecology === 'react') { - const exportedList = await ReactParser.parseExportedStatement( - params.entryFile, - params.sourceType, - ); - if (Array.isArray(exportedList)) { - for (const item of exportedList) { - if (item.source && item.source.length) { - try { - let filePath = join(entryFileDirName, item.source); - if (await this.isDirectory(filePath)) { - filePath = join(filePath, 'index.js'); - } else { - filePath = join(filePath, '.js'); - } - debug('filePath', filePath); - modules.push({ - filePath, - fileContent: await this.loadFile(filePath), - }); - } catch (e) { - debug('error', 'parseEntryFile', e.message); - } - } - } - } - } - debug('modules', modules); - return modules; - } -} - -export default Scanner; diff --git a/packages/material-parser/src/types/IAccesser.ts b/packages/material-parser/src/types/IAccesser.ts index 45cfd2dd3..3b3506118 100644 --- a/packages/material-parser/src/types/IAccesser.ts +++ b/packages/material-parser/src/types/IAccesser.ts @@ -1,4 +1,4 @@ -import { IComponentMaterial } from '../otter-core'; +import { ComponentMeta } from '../otter-core'; /** * 接入器接口(用于定义物料化组件的接入渠道) @@ -10,7 +10,7 @@ interface IAccesser { * @returns {Promise} * @memberof IAccesser */ - access(): Promise; + access(): Promise; } export default IAccesser; diff --git a/packages/material-parser/src/types/IExtensionConfigManifest.ts b/packages/material-parser/src/types/IExtensionConfigManifest.ts index 5971341f3..40dba966f 100644 --- a/packages/material-parser/src/types/IExtensionConfigManifest.ts +++ b/packages/material-parser/src/types/IExtensionConfigManifest.ts @@ -1,15 +1,15 @@ -import { IComponentMaterial } from '../otter-core'; +import { ComponentMeta } from '../otter-core'; /** * 扩展点:配置 manifest * (物料化场景) */ type IExtensionConfigManifest = (params: { - manifestObj: IComponentMaterial; // manifest 配置对象 + manifestObj: ComponentMeta; // manifest 配置对象 manifestFilePath: string; // manifest 文件默认路径 }) => Promise<{ manifestJSON: string; // manifest 文件内容 manifestFilePath: string; // manifest 文件路径 - manifestObj: IComponentMaterial; // manifest 文件对象 + manifestObj: ComponentMeta; // manifest 文件对象 }>; export default IExtensionConfigManifest; diff --git a/packages/material-parser/src/types/IGenerator.ts b/packages/material-parser/src/types/IGenerator.ts deleted file mode 100644 index bd16d9ead..000000000 --- a/packages/material-parser/src/types/IGenerator.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { IComponentMaterial } from '../otter-core'; -import { IMaterialParsedModel } from './IMaterialParsedModel'; -import IMaterialScanModel from './IMaterialScanModel'; - -/** - * 生成器 - */ -export default interface IGenerator { - /** - * 根据前面两阶段的产物生成最终编排引擎需要的物料 - * @param {IMaterialScanModel} matScanModel 对应扫描阶段产物 - * @param {IMaterialParsedModel[]} matParsedModels 对应解析阶段产物 - * @returns {Promise} - * @memberof IGenerator - */ - generate( - matScanModel: IMaterialScanModel, - matParsedModels: IMaterialParsedModel[], - ): Promise; -} diff --git a/packages/material-parser/src/types/IMaterialParsedModel.ts b/packages/material-parser/src/types/IMaterialParsedModel.ts index e86046674..94cce6f75 100644 --- a/packages/material-parser/src/types/IMaterialParsedModel.ts +++ b/packages/material-parser/src/types/IMaterialParsedModel.ts @@ -12,38 +12,38 @@ export interface IPropType { export type IPropTypes = IPropType[]; export interface IMaterialParsedModel { - filePath: string; - defaultExportName: string; + // filePath: string; + componentName: string; props?: PropsSection['props']; - componentNames: Array<{ - exportedName: string; - localName: string; - source?: string; - }>; - importModules: Array<{ - importDefaultName?: string; - importName?: string; - localName?: string; - source: string; - }>; - exportModules: Array<{ - exportedName: string; - localName: string; - source?: string; - }>; - /** - * 子模块,形如:Demo.SubModule = value; 或者 Demo.SubModule.Sub = subValue; - */ - subModules: Array<{ - objectName: string[]; - propertyName: string; - value?: string; - // value 是否对应匿名函数 - isValueAnonymousFunc: boolean; - }>; - propsTypes: IPropTypes; - propsDefaults: Array<{ - name: string; - defaultValue: any; - }>; + // componentNames: { + // exportedName: string; + // localName: string; + // source?: string; + // }[]; + // importModules: { + // importDefaultName?: string; + // importName?: string; + // localName?: string; + // source: string; + // }[]; + // exportModules: { + // exportedName: string; + // localName: string; + // source?: string; + // }[]; + // /** + // * 子模块,形如:Demo.SubModule = value; 或者 Demo.SubModule.Sub = subValue; + // */ + // subModules: { + // objectName: string[]; + // propertyName: string; + // value?: string; + // // value 是否对应匿名函数 + // isValueAnonymousFunc: boolean; + // }[]; + // propsTypes: IPropTypes; + // propsDefaults: { + // name: string; + // defaultValue: any; + // }[]; } diff --git a/packages/material-parser/src/types/IMaterialScanModel.ts b/packages/material-parser/src/types/IMaterialScanModel.ts index 5e9b1551c..ae40a5043 100644 --- a/packages/material-parser/src/types/IMaterialScanModel.ts +++ b/packages/material-parser/src/types/IMaterialScanModel.ts @@ -2,15 +2,14 @@ * 对应扫描阶段的产物 */ interface IMaterialScanModel { - /** 入口文件地址 */ - mainEntry: string; /** 标记物料组件包所使用的模块规范 */ sourceType: 'module' | 'main'; - /** 每个文件对应的文件内容 */ - modules: Array<{ - filePath: string; - fileContent: string; - }>; + /** 入口文件路径 */ + entryFilePath: string; + /** 入口文件内容 */ + entryFileContent: string; + /** main文件相对路径 */ + mainFilePath: string; /** 当前包名 */ pkgName: string; /** 当前包版本 */ diff --git a/packages/material-parser/src/types/IMaterializeOptions.ts b/packages/material-parser/src/types/IMaterializeOptions.ts index 9896f3916..259656f08 100644 --- a/packages/material-parser/src/types/IMaterializeOptions.ts +++ b/packages/material-parser/src/types/IMaterializeOptions.ts @@ -7,7 +7,7 @@ import IExtensionConfigManifest from './IExtensionConfigManifest'; */ interface IMaterializeOptions { /** - * 入口文件路径或包名 + * 组件文件(夹)路径或包名 * 形如: * 本地路径:/usr/project/src/container/DemoMaterial * 包名:@ali/demo-material@0.0.1 @@ -22,21 +22,6 @@ interface IMaterializeOptions { */ accesser: 'local' | 'online'; - /** - * 是否为多组件透出场景 - * (true:表示多组件透出场景,false:表示单组件透出场景) - * @type {boolean} - * @memberof IMaterializeOptions - */ - isExportedAsMultiple: boolean; - - /** - * 当 accesser=local 时,需要通过此配置项指定当前工作目录,形如:/usr/.../demo-project - * @type {string} - * @memberof IMaterializeOptions - */ - cwd?: string; - /** * 扩展点 */ diff --git a/packages/material-parser/src/types/IParser.ts b/packages/material-parser/src/types/IParser.ts deleted file mode 100644 index 3906b5ec9..000000000 --- a/packages/material-parser/src/types/IParser.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { IMaterialParsedModel } from './IMaterialParsedModel'; -import IMaterialScanModel from './IMaterialScanModel'; - -/** - * 解析器 - * @interface IParser - */ -interface IParser { - /** - * 根据 IScanner 阶段的产出结果,解析对文件内容进行 AST 解析 - * @param {IMaterialScanModel} model IScanner 阶段的产出结果 - * @returns {Promise} - * @memberof IParser - */ - parse(model: IMaterialScanModel): Promise; - - /** - * 解析 ES5 语法 - * @param {IMaterialScanModel} model - * @returns {Promise} - * @memberof IParser - */ - parseES5(model: IMaterialScanModel): Promise; - - /** - * 解析 ESM 语法 - * @param {{ - * model: IMaterialScanModel, - * filePath: string, // 要解析的文件路径 - * fileContent: string // 要解析的文件内容 - * }} params - * @returns {Promise} - * @memberof IParser - */ - parseES6(params: { - model: IMaterialScanModel; - filePath: string; - fileContent: string; - }): Promise; -} - -export default IParser; diff --git a/packages/material-parser/src/types/IScanner.ts b/packages/material-parser/src/types/IScanner.ts deleted file mode 100644 index 2c10eda17..000000000 --- a/packages/material-parser/src/types/IScanner.ts +++ /dev/null @@ -1,32 +0,0 @@ -import IMaterialScanModel from './IMaterialScanModel'; - -/** - * 扫描器接口 - * @interface IScanner - */ -interface IScanner { - /** - * 扫描 - * @returns {Promise} 扫描产物 - * @memberof IScanner - */ - scan(): Promise; - - /** - * 加载文件 - * @param {string} filePath 文件地址 - * @returns {Promise} 返回文件内容 - * @memberof IScanner - */ - loadFile(filePath: string): Promise; - - /** - * 解析 package.json 文件 - * @param {string} pkgJsonPath 文件路径 - * @returns {Promise<{ [k: string]: any }>} package 文件信息 - * @memberof IScanner - */ - resolvePkgJson(pkgJsonPath: string): Promise<{ [k: string]: any }>; -} - -export default IScanner; diff --git a/packages/material-parser/src/types/index.ts b/packages/material-parser/src/types/index.ts index 06f5ae80e..adca16e32 100644 --- a/packages/material-parser/src/types/index.ts +++ b/packages/material-parser/src/types/index.ts @@ -4,20 +4,15 @@ import ExtensionName from './ExtensionName'; import IAccesser from './IAccesser'; import ICompiler from './ICompiler'; import IExtensionConfigManifest from './IExtensionConfigManifest'; -import IGenerator from './IGenerator'; import IMaterializeOptions from './IMaterializeOptions'; -export * from './IMaterialParsedModel'; import IMaterialScanModel from './IMaterialScanModel'; -import IParser from './IParser'; -import IScanner from './IScanner'; +import { IMaterialParsedModel } from './IMaterialParsedModel'; import SourceType from './SourceType'; export { - IGenerator, - IParser, - IScanner, ExtensionName, IExtensionConfigManifest, + IMaterialParsedModel, IMaterializeOptions, IMaterialScanModel, SourceType, diff --git a/packages/material-parser/test/Materialize.ts b/packages/material-parser/test/Materialize.ts deleted file mode 100644 index c4c81bd2a..000000000 --- a/packages/material-parser/test/Materialize.ts +++ /dev/null @@ -1,63 +0,0 @@ -import test from 'ava'; -import Materialize from '../src/Materialize'; -import { IMaterializeOptions } from '../src/types'; -import { getFromFixtures } from './helpers'; - -const multiExportedComptPath = getFromFixtures('multiple-exported-component'); -const singleExportedComptPath = getFromFixtures('single-exported-component'); -const singleExportedComponent = '@ali/demo-biz-test090702@0.0.2'; -const multipleExportedComponent = '@ali/aimake-basic@0.1.0'; - -test('materialize single exported component by local', async t => { - const options: IMaterializeOptions = { - cwd: singleExportedComptPath, - entry: singleExportedComptPath, - accesser: 'local', - isExportedAsMultiple: false, - }; - - const instance = new Materialize(options); - const actual = await instance.start(); - - t.snapshot(actual); -}); - -// test('materialize multiple exported component by local', async t => { -// const options: IMaterializeOptions = { -// cwd: multiExportedComptPath, -// entry: multiExportedComptPath, -// accesser: 'local', -// isExportedAsMultiple: true, -// }; - -// const instance = new Materialize(options); -// const actual = await instance.start(); - -// t.snapshot(actual); -// }); - -// test('materialize single exported component by online', async t => { -// const options: IMaterializeOptions = { -// entry: singleExportedComponent, -// accesser: 'online', -// isExportedAsMultiple: false, -// }; - -// const instance = new Materialize(options); -// const actual = await instance.start(); - -// t.snapshot(actual); -// }); - -// test('materialize multiple exported component by online', async t => { -// const options: IMaterializeOptions = { -// entry: multipleExportedComponent, -// accesser: 'online', -// isExportedAsMultiple: false, -// }; - -// const instance = new Materialize(options); -// const actual = await instance.start(); - -// t.snapshot(actual); -// }); diff --git a/packages/material-parser/test/accesser/LocalAccesser.ts b/packages/material-parser/test/accesser/LocalAccesser.ts deleted file mode 100644 index d09786416..000000000 --- a/packages/material-parser/test/accesser/LocalAccesser.ts +++ /dev/null @@ -1,29 +0,0 @@ -import test from 'ava'; -import LocalAccesser from '../../src/accesser/LocalAccesser'; -import { IMaterializeOptions } from '../../src/types'; -import { getFromFixtures } from '../helpers'; - -const multiExportedComptPath = getFromFixtures('multiple-exported-component'); -const singleExportedComptPath = getFromFixtures('single-exported-component'); - -test.serial('access single exported component by local', async t => { - const options: IMaterializeOptions = { - entry: singleExportedComptPath, - accesser: 'local', - isExportedAsMultiple: false, - }; - const accesser = new LocalAccesser(options); - const actual = await accesser.access(); - t.snapshot(actual); -}); - -// test.serial('access multiple exported component by local', async t => { -// const options: IMaterializeOptions = { -// entry: multiExportedComptPath, -// accesser: 'local', -// isExportedAsMultiple: true, -// }; -// const accesser = new LocalAccesser(options); -// const actual = await accesser.access(); -// t.snapshot(actual); -// }); diff --git a/packages/material-parser/test/accesser/OnlineAccesser.ts b/packages/material-parser/test/accesser/OnlineAccesser.ts deleted file mode 100644 index 83aa50963..000000000 --- a/packages/material-parser/test/accesser/OnlineAccesser.ts +++ /dev/null @@ -1,42 +0,0 @@ -import test from 'ava'; -import OnlineAccesser from '../../src/accesser/OnlineAccesser'; -import { IMaterializeOptions } from '../../src/types'; - -const singleExportedComponent = '@ali/demo-biz-test090702@0.0.2'; -const multipleExportedComponent = '@ali/aimake-basic@0.1.0'; - -test.serial('online accesser', t => { - t.pass(); -}) -// test.serial('access single exported component by online', async t => { -// const options: IMaterializeOptions = { -// entry: singleExportedComponent, -// accesser: 'online', -// isExportedAsMultiple: false, -// }; -// const accesser = new OnlineAccesser(options); -// const actual = await accesser.access(); -// t.snapshot(actual); -// }); - -// test.serial('access multiple exported component by online', async t => { -// const options: IMaterializeOptions = { -// entry: multipleExportedComponent, -// accesser: 'online', -// isExportedAsMultiple: true, -// }; -// const accesser = new OnlineAccesser(options); -// const actual = await accesser.access(); -// t.snapshot(actual); -// }); - -// test.serial('access @alifd/next@1.17.12 by online', async t => { -// const options: IMaterializeOptions = { -// entry: '@alifd/next@1.17.12', -// accesser: 'online', -// isExportedAsMultiple: true, -// }; -// const accesser = new OnlineAccesser(options); -// const actual = await accesser.access(); -// t.snapshot(actual); -// }); diff --git a/packages/material-parser/test/accesser/snapshots/LocalAccesser.ts.md b/packages/material-parser/test/accesser/snapshots/LocalAccesser.ts.md deleted file mode 100644 index b6d56ecd5..000000000 --- a/packages/material-parser/test/accesser/snapshots/LocalAccesser.ts.md +++ /dev/null @@ -1,529 +0,0 @@ -# Snapshot report for `test/accesser/LocalAccesser.ts` - -The actual snapshot is saved in `LocalAccesser.ts.snap`. - -Generated by [AVA](https://ava.li). - -## access multiple exported component by local - -> Snapshot 1 - - [ - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/AIMakeBlank/manifest.js', - manifestJS: 'export default {"name":"AIMakeBlank","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"children","label":"children","renderer":""},{"name":"styleBoxModel","label":"styleBoxModel","renderer":""},{"name":"styleLayout","label":"styleLayout","renderer":""},{"name":"styleBackground","label":"styleBackground","renderer":""},{"name":"styleFlexLayout","label":"styleFlexLayout","renderer":""},{"name":"style","label":"style","renderer":"","defaultValue":"{}"},{"name":"id","label":"id","renderer":""}]}}', - manifestObj: { - name: 'AIMakeBlank', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: undefined, - label: 'children', - name: 'children', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBoxModel', - name: 'styleBoxModel', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleLayout', - name: 'styleLayout', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBackground', - name: 'styleBackground', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleFlexLayout', - name: 'styleFlexLayout', - renderer: '', - }, - { - defaultValue: '{}', - label: 'style', - name: 'style', - renderer: '', - }, - { - defaultValue: undefined, - label: 'id', - name: 'id', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/AIMakeIcon/manifest.js', - manifestJS: 'export default {"name":"AIMakeIcon","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"className","label":"className","renderer":""},{"name":"iconClassName","label":"iconClassName","renderer":""},{"name":"children","label":"children","renderer":""},{"name":"styleBoxModel","label":"styleBoxModel","renderer":""},{"name":"styleText","label":"styleText","renderer":""},{"name":"styleBackground","label":"styleBackground","renderer":""},{"name":"style","label":"style","renderer":"","defaultValue":"{}"}]}}', - manifestObj: { - name: 'AIMakeIcon', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: undefined, - label: 'className', - name: 'className', - renderer: '', - }, - { - defaultValue: undefined, - label: 'iconClassName', - name: 'iconClassName', - renderer: '', - }, - { - defaultValue: undefined, - label: 'children', - name: 'children', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBoxModel', - name: 'styleBoxModel', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleText', - name: 'styleText', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBackground', - name: 'styleBackground', - renderer: '', - }, - { - defaultValue: '{}', - label: 'style', - name: 'style', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/AIMakeImage/manifest.js', - manifestJS: 'export default {"name":"AIMakeImage","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"styleBoxModel","label":"styleBoxModel","renderer":""},{"name":"style","label":"style","renderer":"","defaultValue":"{}"}]}}', - manifestObj: { - name: 'AIMakeImage', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: undefined, - label: 'styleBoxModel', - name: 'styleBoxModel', - renderer: '', - }, - { - defaultValue: '{}', - label: 'style', - name: 'style', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/AIMakeLink/manifest.js', - manifestJS: 'export default {"name":"AIMakeLink","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"children","label":"children","renderer":""},{"name":"styleBoxModel","label":"styleBoxModel","renderer":""},{"name":"styleText","label":"styleText","renderer":""},{"name":"styleLayout","label":"styleLayout","renderer":""},{"name":"styleBackground","label":"styleBackground","renderer":""},{"name":"style","label":"style","renderer":"","defaultValue":"{}"}]}}', - manifestObj: { - name: 'AIMakeLink', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: undefined, - label: 'children', - name: 'children', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBoxModel', - name: 'styleBoxModel', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleText', - name: 'styleText', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleLayout', - name: 'styleLayout', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBackground', - name: 'styleBackground', - renderer: '', - }, - { - defaultValue: '{}', - label: 'style', - name: 'style', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/AIMakePlaceholder/manifest.js', - manifestJS: 'export default {"name":"AIMakePlaceholder","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"children","label":"children","renderer":""},{"name":"styleBoxModel","label":"styleBoxModel","renderer":""},{"name":"styleLayout","label":"styleLayout","renderer":""},{"name":"style","label":"style","renderer":"","defaultValue":"{}"}]}}', - manifestObj: { - name: 'AIMakePlaceholder', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: undefined, - label: 'children', - name: 'children', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBoxModel', - name: 'styleBoxModel', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleLayout', - name: 'styleLayout', - renderer: '', - }, - { - defaultValue: '{}', - label: 'style', - name: 'style', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/AIMakeText/manifest.js', - manifestJS: 'export default {"name":"AIMakeText","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"children","label":"children","renderer":""},{"name":"type","label":"type","renderer":""},{"name":"styleBoxModel","label":"styleBoxModel","renderer":""},{"name":"styleText","label":"styleText","renderer":""},{"name":"styleLayout","label":"styleLayout","renderer":""},{"name":"styleBackground","label":"styleBackground","renderer":""},{"name":"style","label":"style","renderer":"","defaultValue":"{}"}]}}', - manifestObj: { - name: 'AIMakeText', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: undefined, - label: 'children', - name: 'children', - renderer: '', - }, - { - defaultValue: undefined, - label: 'type', - name: 'type', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBoxModel', - name: 'styleBoxModel', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleText', - name: 'styleText', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleLayout', - name: 'styleLayout', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBackground', - name: 'styleBackground', - renderer: '', - }, - { - defaultValue: '{}', - label: 'style', - name: 'style', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/Root/manifest.js', - manifestJS: 'export default {"name":"Root","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"style","label":"style","renderer":"","defaultValue":"{\\n padding: 0,\\n backgroundColor: \'#f0f2f5\',\\n minHeight: \'100%\'\\n}"},{"name":"children","label":"children","renderer":""}]}}', - manifestObj: { - name: 'Root', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: `{␊ - padding: 0,␊ - backgroundColor: '#f0f2f5',␊ - minHeight: '100%'␊ - }`, - label: 'style', - name: 'style', - renderer: '', - }, - { - defaultValue: undefined, - label: 'children', - name: 'children', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - ] - -## access single exported component by local - -> Snapshot 1 - - [ - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/single-exported-component/es/manifest.js', - manifestJS: 'export default {"name":"Demo","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"optionalArray","label":"optionalArray","renderer":""},{"name":"optionalBool","label":"optionalBool","renderer":""},{"name":"optionalFunc","label":"optionalFunc","renderer":""},{"name":"optionalNumber","label":"optionalNumber","renderer":""},{"name":"optionalObject","label":"optionalObject","renderer":""},{"name":"optionalString","label":"optionalString","renderer":""},{"name":"optionalSymbol","label":"optionalSymbol","renderer":""},{"name":"optionalNode","label":"optionalNode","renderer":""},{"name":"optionalElement","label":"optionalElement","renderer":""},{"name":"optionalElementType","label":"optionalElementType","renderer":""},{"name":"optionalMessage","label":"optionalMessage","renderer":""},{"name":"optionalEnum","label":"optionalEnum","renderer":""},{"name":"optionalUnion","label":"optionalUnion","renderer":""},{"name":"optionalArrayOf","label":"optionalArrayOf","renderer":""},{"name":"optionalObjectOf","label":"optionalObjectOf","renderer":""},{"name":"optionalObjectWithShape","label":"optionalObjectWithShape","renderer":""},{"name":"optionalObjectWithShape2","label":"optionalObjectWithShape2","renderer":""},{"name":"optionalObjectWithStrictShape","label":"optionalObjectWithStrictShape","renderer":""},{"name":"requiredFunc","label":"requiredFunc","renderer":""},{"name":"requiredAny","label":"requiredAny","renderer":""}]}}', - manifestObj: { - name: 'Demo', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: undefined, - label: 'optionalArray', - name: 'optionalArray', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalBool', - name: 'optionalBool', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalFunc', - name: 'optionalFunc', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalNumber', - name: 'optionalNumber', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalObject', - name: 'optionalObject', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalString', - name: 'optionalString', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalSymbol', - name: 'optionalSymbol', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalNode', - name: 'optionalNode', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalElement', - name: 'optionalElement', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalElementType', - name: 'optionalElementType', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalMessage', - name: 'optionalMessage', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalEnum', - name: 'optionalEnum', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalUnion', - name: 'optionalUnion', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalArrayOf', - name: 'optionalArrayOf', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalObjectOf', - name: 'optionalObjectOf', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalObjectWithShape', - name: 'optionalObjectWithShape', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalObjectWithShape2', - name: 'optionalObjectWithShape2', - renderer: '', - }, - { - defaultValue: undefined, - label: 'optionalObjectWithStrictShape', - name: 'optionalObjectWithStrictShape', - renderer: '', - }, - { - defaultValue: undefined, - label: 'requiredFunc', - name: 'requiredFunc', - renderer: '', - }, - { - defaultValue: undefined, - label: 'requiredAny', - name: 'requiredAny', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - ] diff --git a/packages/material-parser/test/accesser/snapshots/LocalAccesser.ts.snap b/packages/material-parser/test/accesser/snapshots/LocalAccesser.ts.snap deleted file mode 100644 index 0d3c837c2..000000000 Binary files a/packages/material-parser/test/accesser/snapshots/LocalAccesser.ts.snap and /dev/null differ diff --git a/packages/material-parser/test/accesser/snapshots/OnlineAccesser.ts.md b/packages/material-parser/test/accesser/snapshots/OnlineAccesser.ts.md deleted file mode 100644 index fc1e99f97..000000000 --- a/packages/material-parser/test/accesser/snapshots/OnlineAccesser.ts.md +++ /dev/null @@ -1,408 +0,0 @@ -# Snapshot report for `test/accesser/OnlineAccesser.ts` - -The actual snapshot is saved in `OnlineAccesser.ts.snap`. - -Generated by [AVA](https://ava.li). - -## access multiple exported component by online - -> Snapshot 1 - - [ - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/node_modules/.temp/kDKCQ3wGKzpHh6KU5ExdE1/node_modules/@ali/aimake-basic/es/basic/AIMakeBlank/manifest.js', - manifestJS: 'export default {"name":"AIMakeBlank","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"children","label":"children","renderer":""},{"name":"styleBoxModel","label":"styleBoxModel","renderer":""},{"name":"styleLayout","label":"styleLayout","renderer":""},{"name":"styleBackground","label":"styleBackground","renderer":""},{"name":"styleFlexLayout","label":"styleFlexLayout","renderer":""},{"name":"style","label":"style","renderer":"","defaultValue":"{}"},{"name":"id","label":"id","renderer":""}]}}', - manifestObj: { - name: 'AIMakeBlank', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: undefined, - label: 'children', - name: 'children', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBoxModel', - name: 'styleBoxModel', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleLayout', - name: 'styleLayout', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBackground', - name: 'styleBackground', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleFlexLayout', - name: 'styleFlexLayout', - renderer: '', - }, - { - defaultValue: '{}', - label: 'style', - name: 'style', - renderer: '', - }, - { - defaultValue: undefined, - label: 'id', - name: 'id', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/node_modules/.temp/kDKCQ3wGKzpHh6KU5ExdE1/node_modules/@ali/aimake-basic/es/basic/AIMakeIcon/manifest.js', - manifestJS: 'export default {"name":"AIMakeIcon","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"className","label":"className","renderer":""},{"name":"iconClassName","label":"iconClassName","renderer":""},{"name":"children","label":"children","renderer":""},{"name":"styleBoxModel","label":"styleBoxModel","renderer":""},{"name":"styleText","label":"styleText","renderer":""},{"name":"styleBackground","label":"styleBackground","renderer":""},{"name":"style","label":"style","renderer":"","defaultValue":"{}"}]}}', - manifestObj: { - name: 'AIMakeIcon', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: undefined, - label: 'className', - name: 'className', - renderer: '', - }, - { - defaultValue: undefined, - label: 'iconClassName', - name: 'iconClassName', - renderer: '', - }, - { - defaultValue: undefined, - label: 'children', - name: 'children', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBoxModel', - name: 'styleBoxModel', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleText', - name: 'styleText', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBackground', - name: 'styleBackground', - renderer: '', - }, - { - defaultValue: '{}', - label: 'style', - name: 'style', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/node_modules/.temp/kDKCQ3wGKzpHh6KU5ExdE1/node_modules/@ali/aimake-basic/es/basic/AIMakeImage/manifest.js', - manifestJS: 'export default {"name":"AIMakeImage","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"styleBoxModel","label":"styleBoxModel","renderer":""},{"name":"style","label":"style","renderer":"","defaultValue":"{}"}]}}', - manifestObj: { - name: 'AIMakeImage', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: undefined, - label: 'styleBoxModel', - name: 'styleBoxModel', - renderer: '', - }, - { - defaultValue: '{}', - label: 'style', - name: 'style', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/node_modules/.temp/kDKCQ3wGKzpHh6KU5ExdE1/node_modules/@ali/aimake-basic/es/basic/AIMakeLink/manifest.js', - manifestJS: 'export default {"name":"AIMakeLink","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"children","label":"children","renderer":""},{"name":"styleBoxModel","label":"styleBoxModel","renderer":""},{"name":"styleText","label":"styleText","renderer":""},{"name":"styleLayout","label":"styleLayout","renderer":""},{"name":"styleBackground","label":"styleBackground","renderer":""},{"name":"style","label":"style","renderer":"","defaultValue":"{}"}]}}', - manifestObj: { - name: 'AIMakeLink', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: undefined, - label: 'children', - name: 'children', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBoxModel', - name: 'styleBoxModel', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleText', - name: 'styleText', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleLayout', - name: 'styleLayout', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBackground', - name: 'styleBackground', - renderer: '', - }, - { - defaultValue: '{}', - label: 'style', - name: 'style', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/node_modules/.temp/kDKCQ3wGKzpHh6KU5ExdE1/node_modules/@ali/aimake-basic/es/basic/AIMakePlaceholder/manifest.js', - manifestJS: 'export default {"name":"AIMakePlaceholder","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"children","label":"children","renderer":""},{"name":"styleBoxModel","label":"styleBoxModel","renderer":""},{"name":"styleLayout","label":"styleLayout","renderer":""},{"name":"style","label":"style","renderer":"","defaultValue":"{}"}]}}', - manifestObj: { - name: 'AIMakePlaceholder', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: undefined, - label: 'children', - name: 'children', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBoxModel', - name: 'styleBoxModel', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleLayout', - name: 'styleLayout', - renderer: '', - }, - { - defaultValue: '{}', - label: 'style', - name: 'style', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/node_modules/.temp/kDKCQ3wGKzpHh6KU5ExdE1/node_modules/@ali/aimake-basic/es/basic/AIMakeText/manifest.js', - manifestJS: 'export default {"name":"AIMakeText","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"children","label":"children","renderer":""},{"name":"type","label":"type","renderer":""},{"name":"styleBoxModel","label":"styleBoxModel","renderer":""},{"name":"styleText","label":"styleText","renderer":""},{"name":"styleLayout","label":"styleLayout","renderer":""},{"name":"styleBackground","label":"styleBackground","renderer":""},{"name":"style","label":"style","renderer":"","defaultValue":"{}"}]}}', - manifestObj: { - name: 'AIMakeText', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: undefined, - label: 'children', - name: 'children', - renderer: '', - }, - { - defaultValue: undefined, - label: 'type', - name: 'type', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBoxModel', - name: 'styleBoxModel', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleText', - name: 'styleText', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleLayout', - name: 'styleLayout', - renderer: '', - }, - { - defaultValue: undefined, - label: 'styleBackground', - name: 'styleBackground', - renderer: '', - }, - { - defaultValue: '{}', - label: 'style', - name: 'style', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/node_modules/.temp/kDKCQ3wGKzpHh6KU5ExdE1/node_modules/@ali/aimake-basic/es/basic/Root/manifest.js', - manifestJS: 'export default {"name":"Root","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[{"name":"style","label":"style","renderer":"","defaultValue":"{\\n padding: 0,\\n backgroundColor: \'#f0f2f5\',\\n minHeight: \'100%\'\\n}"},{"name":"children","label":"children","renderer":""}]}}', - manifestObj: { - name: 'Root', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [ - { - defaultValue: `{␊ - padding: 0,␊ - backgroundColor: '#f0f2f5',␊ - minHeight: '100%'␊ - }`, - label: 'style', - name: 'style', - renderer: '', - }, - { - defaultValue: undefined, - label: 'children', - name: 'children', - renderer: '', - }, - ], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - ] - -## access single exported component by online - -> Snapshot 1 - - [ - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/node_modules/.temp/todtaTTK9KxDGepu5Kbb9G/node_modules/@ali/demo-biz-test090702/es/manifest.js', - manifestJS: 'export default {"name":"Demo","settings":{"type":"element_inline","insertionModes":"tbrl","handles":["cut","copy","duplicate","delete","paste"],"shouldActive":true,"shouldDrag":true,"props":[]}}', - manifestObj: { - name: 'Demo', - settings: { - handles: [ - 'cut', - 'copy', - 'duplicate', - 'delete', - 'paste', - ], - insertionModes: 'tbrl', - props: [], - shouldActive: true, - shouldDrag: true, - type: 'element_inline', - }, - }, - }, - ] diff --git a/packages/material-parser/test/accesser/snapshots/OnlineAccesser.ts.snap b/packages/material-parser/test/accesser/snapshots/OnlineAccesser.ts.snap deleted file mode 100644 index e8ea50d94..000000000 Binary files a/packages/material-parser/test/accesser/snapshots/OnlineAccesser.ts.snap and /dev/null differ diff --git a/packages/material-parser/test/fixtures/__snapshots__/test/Materialize.ts.md b/packages/material-parser/test/fixtures/__snapshots__/test/Materialize.ts.md deleted file mode 100644 index 7a2d6f6db..000000000 --- a/packages/material-parser/test/fixtures/__snapshots__/test/Materialize.ts.md +++ /dev/null @@ -1,188 +0,0 @@ -# Snapshot report for `test/Materialize.ts` - -The actual snapshot is saved in `Materialize.ts.snap`. - -Generated by [AVA](https://avajs.dev). - -## materialize single exported component by local - -> Snapshot 1 - - [ - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/single-exported-component/es/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/single-exported-component/es/index.js', - package: 'single-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'optionalArray', - propType: 'array', - }, - { - name: 'optionalBool', - propType: 'bool', - }, - { - name: 'optionalFunc', - propType: 'func', - }, - { - defaultValue: 123, - name: 'optionalNumber', - propType: 'number', - }, - { - name: 'optionalObject', - propType: 'object', - }, - { - name: 'optionalString', - propType: 'string', - }, - { - name: 'optionalSymbol', - propType: 'symbol', - }, - { - name: 'optionalNode', - propType: 'node', - }, - { - name: 'optionalElement', - propType: 'element', - }, - { - name: 'optionalElementType', - propType: 'elementType', - }, - { - name: 'optionalMessage', - propType: { - type: 'instanceOf', - value: 'Demo', - }, - }, - { - name: 'optionalEnum', - propType: { - type: 'oneOf', - value: [ - 'News', - 'Photos', - ], - }, - }, - { - name: 'optionalUnion', - propType: { - type: 'oneOfType', - value: [ - 'string', - 'number', - { - type: 'instanceOf', - value: 'Demo', - }, - ], - }, - }, - { - name: 'optionalArrayOf', - propType: { - type: 'arrayOf', - value: 'number', - }, - }, - { - name: 'optionalObjectOf', - propType: { - type: 'objectOf', - value: 'number', - }, - }, - { - name: 'optionalObjectWithShape', - propType: { - type: 'shape', - value: [ - { - name: 'optionalProperty', - propType: 'string', - }, - { - name: 'requiredProperty', - propType: { - isRequired: true, - type: 'number', - }, - }, - ], - }, - }, - { - name: 'optionalObjectWithShape2', - propType: { - isRequired: true, - type: 'shape', - value: [ - { - name: 'optionalProperty', - propType: 'string', - }, - { - name: 'requiredProperty', - propType: { - isRequired: true, - type: 'number', - }, - }, - ], - }, - }, - { - name: 'optionalObjectWithStrictShape', - propType: { - type: 'exact', - value: [ - { - name: 'optionalProperty', - propType: 'string', - }, - { - name: 'requiredProperty', - propType: { - isRequired: true, - type: 'number', - }, - }, - ], - }, - }, - { - name: 'requiredFunc', - propType: { - isRequired: true, - type: 'func', - }, - }, - { - name: 'requiredAny', - propType: { - isRequired: true, - type: 'any', - }, - }, - ], - screenshot: '', - title: 'single-exported-component', - }, - }, - ] diff --git a/packages/material-parser/test/fixtures/__snapshots__/test/Materialize.ts.snap b/packages/material-parser/test/fixtures/__snapshots__/test/Materialize.ts.snap deleted file mode 100644 index c49d07908..000000000 Binary files a/packages/material-parser/test/fixtures/__snapshots__/test/Materialize.ts.snap and /dev/null differ diff --git a/packages/material-parser/test/fixtures/__snapshots__/test/accesser/LocalAccesser.ts.md b/packages/material-parser/test/fixtures/__snapshots__/test/accesser/LocalAccesser.ts.md deleted file mode 100644 index 27bbbe3db..000000000 --- a/packages/material-parser/test/fixtures/__snapshots__/test/accesser/LocalAccesser.ts.md +++ /dev/null @@ -1,188 +0,0 @@ -# Snapshot report for `test/accesser/LocalAccesser.ts` - -The actual snapshot is saved in `LocalAccesser.ts.snap`. - -Generated by [AVA](https://avajs.dev). - -## access single exported component by local - -> Snapshot 1 - - [ - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/single-exported-component/es/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/single-exported-component/es/index.js', - package: '@ali/lowcode-engine-material-parser', - subName: '', - version: '0.1.0', - }, - props: [ - { - name: 'optionalArray', - propType: 'array', - }, - { - name: 'optionalBool', - propType: 'bool', - }, - { - name: 'optionalFunc', - propType: 'func', - }, - { - defaultValue: 123, - name: 'optionalNumber', - propType: 'number', - }, - { - name: 'optionalObject', - propType: 'object', - }, - { - name: 'optionalString', - propType: 'string', - }, - { - name: 'optionalSymbol', - propType: 'symbol', - }, - { - name: 'optionalNode', - propType: 'node', - }, - { - name: 'optionalElement', - propType: 'element', - }, - { - name: 'optionalElementType', - propType: 'elementType', - }, - { - name: 'optionalMessage', - propType: { - type: 'instanceOf', - value: 'Demo', - }, - }, - { - name: 'optionalEnum', - propType: { - type: 'oneOf', - value: [ - 'News', - 'Photos', - ], - }, - }, - { - name: 'optionalUnion', - propType: { - type: 'oneOfType', - value: [ - 'string', - 'number', - { - type: 'instanceOf', - value: 'Demo', - }, - ], - }, - }, - { - name: 'optionalArrayOf', - propType: { - type: 'arrayOf', - value: 'number', - }, - }, - { - name: 'optionalObjectOf', - propType: { - type: 'objectOf', - value: 'number', - }, - }, - { - name: 'optionalObjectWithShape', - propType: { - type: 'shape', - value: [ - { - name: 'optionalProperty', - propType: 'string', - }, - { - name: 'requiredProperty', - propType: { - isRequired: true, - type: 'number', - }, - }, - ], - }, - }, - { - name: 'optionalObjectWithShape2', - propType: { - isRequired: true, - type: 'shape', - value: [ - { - name: 'optionalProperty', - propType: 'string', - }, - { - name: 'requiredProperty', - propType: { - isRequired: true, - type: 'number', - }, - }, - ], - }, - }, - { - name: 'optionalObjectWithStrictShape', - propType: { - type: 'exact', - value: [ - { - name: 'optionalProperty', - propType: 'string', - }, - { - name: 'requiredProperty', - propType: { - isRequired: true, - type: 'number', - }, - }, - ], - }, - }, - { - name: 'requiredFunc', - propType: { - isRequired: true, - type: 'func', - }, - }, - { - name: 'requiredAny', - propType: { - isRequired: true, - type: 'any', - }, - }, - ], - screenshot: '', - title: '@ali/lowcode-engine-material-parser', - }, - }, - ] diff --git a/packages/material-parser/test/fixtures/__snapshots__/test/accesser/LocalAccesser.ts.snap b/packages/material-parser/test/fixtures/__snapshots__/test/accesser/LocalAccesser.ts.snap deleted file mode 100644 index 368768b7c..000000000 Binary files a/packages/material-parser/test/fixtures/__snapshots__/test/accesser/LocalAccesser.ts.snap and /dev/null differ diff --git a/packages/material-parser/test/fixtures/__snapshots__/test/generator/Generator.ts.md b/packages/material-parser/test/fixtures/__snapshots__/test/generator/Generator.ts.md deleted file mode 100644 index d01bf610e..000000000 --- a/packages/material-parser/test/fixtures/__snapshots__/test/generator/Generator.ts.md +++ /dev/null @@ -1,1135 +0,0 @@ -# Snapshot report for `test/generator/Generator.ts` - -The actual snapshot is saved in `Generator.ts.snap`. - -Generated by [AVA](https://avajs.dev). - -## generate multiple exported components - -> Snapshot 1 - - [ - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/AIMakeBlank/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'children', - propType: { - type: 'oneOfType', - value: [ - { - type: 'arrayOf', - value: 'node', - }, - 'node', - ], - }, - }, - { - name: 'styleBoxModel', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleLayout', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleBackground', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleFlexLayout', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'style', - propType: 'object', - }, - { - name: 'id', - propType: 'string', - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/AIMakeIcon/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'className', - propType: 'string', - }, - { - name: 'iconClassName', - propType: 'string', - }, - { - name: 'children', - propType: { - type: 'oneOfType', - value: [ - { - type: 'arrayOf', - value: 'node', - }, - 'node', - ], - }, - }, - { - name: 'styleBoxModel', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleText', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleBackground', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'style', - propType: 'object', - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/AIMakeImage/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'styleBoxModel', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'style', - propType: 'object', - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/AIMakeLink/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'children', - propType: { - type: 'oneOfType', - value: [ - { - type: 'arrayOf', - value: 'node', - }, - 'node', - ], - }, - }, - { - name: 'styleBoxModel', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleText', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleLayout', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleBackground', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'style', - propType: 'object', - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/AIMakePlaceholder/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'children', - propType: { - type: 'oneOfType', - value: [ - { - type: 'arrayOf', - value: 'node', - }, - 'node', - ], - }, - }, - { - name: 'styleBoxModel', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleLayout', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'style', - propType: 'object', - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/AIMakeText/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'children', - propType: { - type: 'oneOfType', - value: [ - { - type: 'arrayOf', - value: 'node', - }, - 'node', - 'string', - ], - }, - }, - { - name: 'type', - propType: 'string', - }, - { - name: 'styleBoxModel', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleText', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleLayout', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleBackground', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'style', - propType: 'object', - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/basic/Root/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'style', - propType: 'object', - }, - { - name: 'children', - propType: { - type: 'oneOfType', - value: [ - 'element', - { - type: 'arrayOf', - value: 'element', - }, - ], - }, - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - ] - -## generate multiple exported components with extensions - -> Snapshot 1 - - [ - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/src/basic/AIMakeBlank/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'children', - propType: { - type: 'oneOfType', - value: [ - { - type: 'arrayOf', - value: 'node', - }, - 'node', - ], - }, - }, - { - name: 'styleBoxModel', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleLayout', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleBackground', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleFlexLayout', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'style', - propType: 'object', - }, - { - name: 'id', - propType: 'string', - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/src/basic/AIMakeIcon/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'className', - propType: 'string', - }, - { - name: 'iconClassName', - propType: 'string', - }, - { - name: 'children', - propType: { - type: 'oneOfType', - value: [ - { - type: 'arrayOf', - value: 'node', - }, - 'node', - ], - }, - }, - { - name: 'styleBoxModel', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleText', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleBackground', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'style', - propType: 'object', - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/src/basic/AIMakeImage/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'styleBoxModel', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'style', - propType: 'object', - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/src/basic/AIMakeLink/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'children', - propType: { - type: 'oneOfType', - value: [ - { - type: 'arrayOf', - value: 'node', - }, - 'node', - ], - }, - }, - { - name: 'styleBoxModel', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleText', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleLayout', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleBackground', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'style', - propType: 'object', - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/src/basic/AIMakePlaceholder/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'children', - propType: { - type: 'oneOfType', - value: [ - { - type: 'arrayOf', - value: 'node', - }, - 'node', - ], - }, - }, - { - name: 'styleBoxModel', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleLayout', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'style', - propType: 'object', - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/src/basic/AIMakeText/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'children', - propType: { - type: 'oneOfType', - value: [ - { - type: 'arrayOf', - value: 'node', - }, - 'node', - 'string', - ], - }, - }, - { - name: 'type', - propType: 'string', - }, - { - name: 'styleBoxModel', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleText', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleLayout', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'styleBackground', - propType: { - isRequired: true, - type: 'object', - }, - }, - { - name: 'style', - propType: 'object', - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/src/basic/Root/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/multiple-exported-component/es/index.js', - package: 'multiple-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'style', - propType: 'object', - }, - { - name: 'children', - propType: { - type: 'oneOfType', - value: [ - 'element', - { - type: 'arrayOf', - value: 'element', - }, - ], - }, - }, - ], - screenshot: '', - title: 'multiple-exported-component', - }, - }, - ] - -## generate single exported components - -> Snapshot 1 - - [ - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/single-exported-component/es/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/single-exported-component/es/index.js', - package: 'single-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'optionalArray', - propType: 'array', - }, - { - name: 'optionalBool', - propType: 'bool', - }, - { - name: 'optionalFunc', - propType: 'func', - }, - { - defaultValue: 123, - name: 'optionalNumber', - propType: 'number', - }, - { - name: 'optionalObject', - propType: 'object', - }, - { - name: 'optionalString', - propType: 'string', - }, - { - name: 'optionalSymbol', - propType: 'symbol', - }, - { - name: 'optionalNode', - propType: 'node', - }, - { - name: 'optionalElement', - propType: 'element', - }, - { - name: 'optionalElementType', - propType: 'elementType', - }, - { - name: 'optionalMessage', - propType: { - type: 'instanceOf', - value: 'Demo', - }, - }, - { - name: 'optionalEnum', - propType: { - type: 'oneOf', - value: [ - 'News', - 'Photos', - ], - }, - }, - { - name: 'optionalUnion', - propType: { - type: 'oneOfType', - value: [ - 'string', - 'number', - { - type: 'instanceOf', - value: 'Demo', - }, - ], - }, - }, - { - name: 'optionalArrayOf', - propType: { - type: 'arrayOf', - value: 'number', - }, - }, - { - name: 'optionalObjectOf', - propType: { - type: 'objectOf', - value: 'number', - }, - }, - { - name: 'optionalObjectWithShape', - propType: { - type: 'shape', - value: [ - { - name: 'optionalProperty', - propType: 'string', - }, - { - name: 'requiredProperty', - propType: { - isRequired: true, - type: 'number', - }, - }, - ], - }, - }, - { - name: 'optionalObjectWithShape2', - propType: { - isRequired: true, - type: 'shape', - value: [ - { - name: 'optionalProperty', - propType: 'string', - }, - { - name: 'requiredProperty', - propType: { - isRequired: true, - type: 'number', - }, - }, - ], - }, - }, - { - name: 'optionalObjectWithStrictShape', - propType: { - type: 'exact', - value: [ - { - name: 'optionalProperty', - propType: 'string', - }, - { - name: 'requiredProperty', - propType: { - isRequired: true, - type: 'number', - }, - }, - ], - }, - }, - { - name: 'requiredFunc', - propType: { - isRequired: true, - type: 'func', - }, - }, - { - name: 'requiredAny', - propType: { - isRequired: true, - type: 'any', - }, - }, - ], - screenshot: '', - title: 'single-exported-component', - }, - }, - ] - -## generate single exported components with extensions - -> Snapshot 1 - - [ - { - manifestFilePath: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/single-exported-component/src/manifest.json', - manifestObj: { - docUrl: '', - npm: { - destructuring: false, - exportName: '', - main: '/Users/gengyang/code/frontend/low-code/ali-lowcode-engine/packages/material-parser/test/fixtures/single-exported-component/es/index.js', - package: 'single-exported-component', - subName: '', - version: '1.0.0', - }, - props: [ - { - name: 'optionalArray', - propType: 'array', - }, - { - name: 'optionalBool', - propType: 'bool', - }, - { - name: 'optionalFunc', - propType: 'func', - }, - { - defaultValue: 123, - name: 'optionalNumber', - propType: 'number', - }, - { - name: 'optionalObject', - propType: 'object', - }, - { - name: 'optionalString', - propType: 'string', - }, - { - name: 'optionalSymbol', - propType: 'symbol', - }, - { - name: 'optionalNode', - propType: 'node', - }, - { - name: 'optionalElement', - propType: 'element', - }, - { - name: 'optionalElementType', - propType: 'elementType', - }, - { - name: 'optionalMessage', - propType: { - type: 'instanceOf', - value: 'Demo', - }, - }, - { - name: 'optionalEnum', - propType: { - type: 'oneOf', - value: [ - 'News', - 'Photos', - ], - }, - }, - { - name: 'optionalUnion', - propType: { - type: 'oneOfType', - value: [ - 'string', - 'number', - { - type: 'instanceOf', - value: 'Demo', - }, - ], - }, - }, - { - name: 'optionalArrayOf', - propType: { - type: 'arrayOf', - value: 'number', - }, - }, - { - name: 'optionalObjectOf', - propType: { - type: 'objectOf', - value: 'number', - }, - }, - { - name: 'optionalObjectWithShape', - propType: { - type: 'shape', - value: [ - { - name: 'optionalProperty', - propType: 'string', - }, - { - name: 'requiredProperty', - propType: { - isRequired: true, - type: 'number', - }, - }, - ], - }, - }, - { - name: 'optionalObjectWithShape2', - propType: { - isRequired: true, - type: 'shape', - value: [ - { - name: 'optionalProperty', - propType: 'string', - }, - { - name: 'requiredProperty', - propType: { - isRequired: true, - type: 'number', - }, - }, - ], - }, - }, - { - name: 'optionalObjectWithStrictShape', - propType: { - type: 'exact', - value: [ - { - name: 'optionalProperty', - propType: 'string', - }, - { - name: 'requiredProperty', - propType: { - isRequired: true, - type: 'number', - }, - }, - ], - }, - }, - { - name: 'requiredFunc', - propType: { - isRequired: true, - type: 'func', - }, - }, - { - name: 'requiredAny', - propType: { - isRequired: true, - type: 'any', - }, - }, - ], - screenshot: '', - title: 'single-exported-component', - }, - }, - ] diff --git a/packages/material-parser/test/fixtures/__snapshots__/test/generator/Generator.ts.snap b/packages/material-parser/test/fixtures/__snapshots__/test/generator/Generator.ts.snap deleted file mode 100644 index 6da53d5f1..000000000 Binary files a/packages/material-parser/test/fixtures/__snapshots__/test/generator/Generator.ts.snap and /dev/null differ diff --git a/packages/material-parser/test/fixtures/__snapshots__/test/index.ts.md b/packages/material-parser/test/fixtures/__snapshots__/test/index.ts.md new file mode 100644 index 000000000..79bf25dc7 --- /dev/null +++ b/packages/material-parser/test/fixtures/__snapshots__/test/index.ts.md @@ -0,0 +1,9746 @@ +# Snapshot report for `test/index.ts` + +The actual snapshot is saved in `index.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## fusion next component by local + +> Snapshot 1 + + [ + { + componentName: 'Affix', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Affix', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + defaultValue: Function {}, + description: `设置 Affix 需要监听滚动事件的容器元素␊ + @return {ReactElement} 目标容器元素的实例`, + name: 'container', + propType: 'func', + }, + { + description: '距离窗口顶部达到指定偏移量后触发', + name: 'offsetTop', + propType: 'number', + }, + { + description: '距离窗口底部达到制定偏移量后触发', + name: 'offsetBottom', + propType: 'number', + }, + { + description: `当元素的样式发生固钉样式变化时触发的回调函数␊ + @param {Boolean} affixed 元素是否被固钉`, + name: 'onAffix', + propType: 'func', + }, + { + description: `是否启用绝对布局实现 affix␊ + @param {Boolean} 是否启用绝对布局`, + name: 'useAbsolute', + propType: 'bool', + }, + { + name: 'className', + propType: 'string', + }, + { + name: 'style', + propType: 'object', + }, + { + name: 'children', + propType: 'any', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Animate', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Animate', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + description: '动画 className', + name: 'animation', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'object', + ], + }, + }, + { + defaultValue: true, + description: '子元素第一次挂载时是否执行动画', + name: 'animationAppear', + propType: 'bool', + }, + { + defaultValue: 'div', + description: '包裹子元素的标签', + name: 'component', + propType: 'any', + }, + { + defaultValue: true, + description: '是否只有单个子元素,如果有多个子元素,请设置为 false', + name: 'singleMode', + propType: 'bool', + }, + { + description: '子元素', + name: 'children', + propType: { + type: 'oneOfType', + value: [ + 'element', + { + type: 'arrayOf', + value: 'element', + }, + ], + }, + }, + { + defaultValue: Function {}, + description: `执行第一次挂载动画前触发的回调函数␊ + @param {HTMLElement} node 执行动画的 dom 元素`, + name: 'beforeAppear', + propType: 'func', + }, + { + defaultValue: Function {}, + description: `执行第一次挂载动画,添加 xxx-appear-active 类名后触发的回调函数␊ + @param {HTMLElement} node 执行动画的 dom 元素`, + name: 'onAppear', + propType: 'func', + }, + { + defaultValue: Function {}, + description: `执行完第一次挂载动画后触发的函数␊ + @param {HTMLElement} node 执行动画的 dom 元素`, + name: 'afterAppear', + propType: 'func', + }, + { + defaultValue: Function {}, + description: `执行进场动画前触发的回调函数␊ + @param {HTMLElement} node 执行动画的 dom 元素`, + name: 'beforeEnter', + propType: 'func', + }, + { + defaultValue: Function {}, + description: `执行进场动画,添加 xxx-enter-active 类名后触发的回调函数␊ + @param {HTMLElement} node 执行动画的 dom 元素`, + name: 'onEnter', + propType: 'func', + }, + { + defaultValue: Function {}, + description: `执行完进场动画后触发的回调函数␊ + @param {HTMLElement} node 执行动画的 dom 元素`, + name: 'afterEnter', + propType: 'func', + }, + { + defaultValue: Function {}, + description: `执行离场动画前触发的回调函数␊ + @param {HTMLElement} node 执行动画的 dom 元素`, + name: 'beforeLeave', + propType: 'func', + }, + { + defaultValue: Function {}, + description: `执行离场动画,添加 xxx-leave-active 类名后触发的回调函数␊ + @param {HTMLElement} node 执行动画的 dom 元素`, + name: 'onLeave', + propType: 'func', + }, + { + defaultValue: Function {}, + description: `执行完离场动画后触发的回调函数␊ + @param {HTMLElement} node 执行动画的 dom 元素`, + name: 'afterLeave', + propType: 'func', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Badge', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Badge', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + name: 'rtl', + propType: 'bool', + }, + { + name: 'className', + propType: 'string', + }, + { + name: 'style', + propType: 'object', + }, + { + description: '徽章依托的内容', + name: 'children', + propType: 'node', + }, + { + defaultValue: 0, + description: '展示的数字,大于 overflowCount 时显示为 ${overflowCount}+,为 0 时默认隐藏', + name: 'count', + propType: { + type: 'oneOfType', + value: [ + 'number', + 'string', + ], + }, + }, + { + defaultValue: false, + description: '当count为0时,是否显示count', + name: 'showZero', + propType: 'bool', + }, + { + description: '自定义节点内容', + name: 'content', + propType: 'node', + }, + { + defaultValue: 99, + description: '展示的封顶的数字', + name: 'overflowCount', + propType: { + type: 'oneOfType', + value: [ + 'number', + 'string', + ], + }, + }, + { + defaultValue: false, + description: '不展示数字,只展示一个小红点', + name: 'dot', + propType: 'bool', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Balloon', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Balloon', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + defaultValue: false, + name: 'pure', + propType: 'bool', + }, + { + name: 'rtl', + propType: 'bool', + }, + { + description: '自定义类名', + name: 'className', + propType: 'string', + }, + { + description: '自定义内敛样式', + name: 'style', + propType: 'object', + }, + { + description: '浮层的内容', + name: 'children', + propType: 'any', + }, + { + defaultValue: 'medium', + name: 'size', + propType: 'string', + }, + { + defaultValue: 'normal', + description: '样式类型', + name: 'type', + propType: { + type: 'oneOf', + value: [ + 'normal', + 'primary', + ], + }, + }, + { + description: '弹层当前显示的状态', + name: 'visible', + propType: 'bool', + }, + { + defaultValue: false, + description: '弹层默认显示的状态', + name: 'defaultVisible', + propType: 'bool', + }, + { + description: `弹层在显示和隐藏触发的事件␊ + @param {Boolean} visible 弹层是否隐藏和显示␊ + @param {String} type 触发弹层显示或隐藏的来源, closeClick 表示由自带的关闭按钮触发; fromTrigger 表示由trigger的点击触发; docClick 表示由document的点击触发`, + name: 'onVisibleChange', + propType: 'func', + }, + { + defaultValue: false, + description: '弹出层对齐方式, 是否为边缘对齐', + name: 'alignEdge', + propType: 'bool', + }, + { + defaultValue: true, + description: '是否显示关闭按钮', + name: 'closable', + propType: 'bool', + }, + { + defaultValue: 'b', + description: `弹出层位置␊ + @enumdesc 上, 右, 下, 左, 上左, 上右, 下左, 下右, 左上, 左下, 右上, 右下 及其 两两组合`, + name: 'align', + propType: { + type: 'oneOf', + value: [ + 't', + 'r', + 'b', + 'l', + 'tl', + 'tr', + 'bl', + 'br', + 'lt', + 'lb', + 'rt', + 'rb', + ], + }, + }, + { + defaultValue: [ + 0, + 0, + ], + description: `弹层相对于trigger的定位的微调, 接收数组[hoz, ver], 表示弹层在 left / top 上的增量␊ + e.g. [100, 100] 表示往右(RTL 模式下是往左) 、下分布偏移100px`, + name: 'offset', + propType: 'array', + }, + { + description: '触发元素', + name: 'trigger', + propType: 'any', + }, + { + defaultValue: 'hover', + description: `触发行为␊ + 鼠标悬浮, 鼠标点击('hover','click')或者它们组成的数组,如 ['hover', 'click'], 强烈不建议使用'focus',若弹窗内容有复杂交互请使用click`, + name: 'triggerType', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'array', + ], + }, + }, + { + name: 'onClick', + propType: 'func', + }, + { + description: '任何visible为false时会触发的事件', + name: 'onClose', + propType: 'func', + }, + { + name: 'onHover', + propType: 'func', + }, + { + defaultValue: false, + description: '是否进行自动位置调整', + name: 'needAdjust', + propType: 'bool', + }, + { + description: '弹层在触发以后的延时显示, 单位毫秒 ms', + name: 'delay', + propType: 'number', + }, + { + description: '浮层关闭后触发的事件, 如果有动画,则在动画结束后触发', + name: 'afterClose', + propType: 'func', + }, + { + description: '强制更新定位信息', + name: 'shouldUpdatePosition', + propType: 'bool', + }, + { + defaultValue: true, + description: '弹层出现后是否自动focus到内部第一个元素', + name: 'autoFocus', + propType: 'bool', + }, + { + defaultValue: undefined, + description: '安全节点:对于triggetType为click的浮层,会在点击除了浮层外的其它区域时关闭浮层.safeNode用于添加不触发关闭的节点, 值可以是dom节点的id或者是节点的dom对象', + name: 'safeNode', + propType: 'string', + }, + { + defaultValue: null, + description: '用来指定safeNode节点的id,和safeNode配合使用', + name: 'safeId', + propType: 'string', + }, + { + description: `配置动画的播放方式␊ + @param {String} in 进场动画␊ + @param {String} out 出场动画`, + name: 'animation', + propType: { + type: 'oneOfType', + value: [ + 'object', + 'bool', + ], + }, + }, + { + defaultValue: false, + description: '弹层的dom节点关闭时是否删除', + name: 'cache', + propType: 'bool', + }, + { + description: '指定浮层渲染的父节点, 可以为节点id的字符串,也可以返回节点的函数。', + name: 'popupContainer', + propType: 'any', + }, + { + name: 'container', + propType: 'any', + }, + { + defaultValue: undefined, + description: '弹层组件style,透传给Popup', + name: 'popupStyle', + propType: 'object', + }, + { + defaultValue: '', + description: '弹层组件className,透传给Popup', + name: 'popupClassName', + propType: 'string', + }, + { + defaultValue: undefined, + description: '弹层组件属性,透传给Popup', + name: 'popupProps', + propType: 'object', + }, + { + description: '是否跟随滚动', + name: 'followTrigger', + propType: 'bool', + }, + { + description: '弹层id, 传入值才会支持无障碍', + name: 'id', + propType: 'string', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Breadcrumb', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Breadcrumb', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + description: '样式类名的品牌前缀', + name: 'prefix', + propType: 'string', + }, + { + name: 'rtl', + propType: 'bool', + }, + { + description: '面包屑子节点,需传入 Breadcrumb.Item', + name: 'children', + propType: 'custom', + }, + { + defaultValue: 100, + description: '面包屑最多显示个数,超出部分会被隐藏, 设置为 auto 会自动根据父元素的宽度适配。', + name: 'maxNode', + propType: { + type: 'oneOfType', + value: [ + 'number', + { + type: 'oneOf', + value: [ + 'auto', + ], + }, + ], + }, + }, + { + description: '分隔符,可以是文本或 Icon', + name: 'separator', + propType: 'node', + }, + { + defaultValue: 'nav', + description: '设置标签类型', + name: 'component', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'func', + ], + }, + }, + { + name: 'className', + propType: 'any', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Button', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Button', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + name: 'rtl', + propType: 'bool', + }, + { + defaultValue: 'normal', + description: '按钮的类型', + name: 'type', + propType: { + type: 'oneOf', + value: [ + 'primary', + 'secondary', + 'normal', + ], + }, + }, + { + defaultValue: 'medium', + description: '按钮的尺寸', + name: 'size', + propType: { + type: 'oneOf', + value: [ + 'small', + 'medium', + 'large', + ], + }, + }, + { + description: '按钮中 Icon 的尺寸,用于替代 Icon 的默认大小', + name: 'iconSize', + propType: { + type: 'oneOf', + value: [ + 'xxs', + 'xs', + 'small', + 'medium', + 'large', + 'xl', + 'xxl', + 'xxxl', + ], + }, + }, + { + defaultValue: 'button', + description: '当 component = \'button\' 时,设置 button 标签的 type 值', + name: 'htmlType', + propType: { + type: 'oneOf', + value: [ + 'submit', + 'reset', + 'button', + ], + }, + }, + { + defaultValue: 'button', + description: '设置标签类型', + name: 'component', + propType: { + type: 'oneOf', + value: [ + 'button', + 'a', + 'div', + 'span', + ], + }, + }, + { + defaultValue: false, + description: '设置按钮的载入状态', + name: 'loading', + propType: 'bool', + }, + { + defaultValue: false, + description: '是否为幽灵按钮', + name: 'ghost', + propType: { + type: 'oneOf', + value: [ + true, + false, + 'light', + 'dark', + ], + }, + }, + { + defaultValue: false, + description: '是否为文本按钮', + name: 'text', + propType: 'bool', + }, + { + defaultValue: false, + description: '是否为警告按钮', + name: 'warning', + propType: 'bool', + }, + { + defaultValue: false, + description: '是否禁用', + name: 'disabled', + propType: 'bool', + }, + { + defaultValue: Function {}, + description: `点击按钮的回调␊ + @param {Object} e Event Object`, + name: 'onClick', + propType: 'func', + }, + { + name: 'className', + propType: 'string', + }, + { + name: 'onMouseUp', + propType: 'func', + }, + { + name: 'children', + propType: 'node', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Calendar', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Calendar', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + defaultValue: false, + name: 'rtl', + propType: 'bool', + }, + { + description: '默认选中的日期(moment 对象)', + name: 'defaultValue', + propType: 'custom', + }, + { + description: '选中的日期值 (moment 对象)', + name: 'value', + propType: 'custom', + }, + { + name: 'modes', + propType: 'array', + }, + { + defaultValue: false, + name: 'disableChangeMode', + propType: 'bool', + }, + { + defaultValue: 'YYYY-MM-DD', + name: 'format', + propType: 'string', + }, + { + defaultValue: true, + description: '是否展示非本月的日期', + name: 'showOtherMonth', + propType: 'bool', + }, + { + description: '默认展示的月份', + name: 'defaultVisibleMonth', + propType: 'func', + }, + { + defaultValue: 'fullscreen', + description: '展现形态', + name: 'shape', + propType: { + type: 'oneOf', + value: [ + 'card', + 'fullscreen', + 'panel', + ], + }, + }, + { + description: `选择日期单元格时的回调␊ + @param {Object} value 对应的日期值 (moment 对象)`, + name: 'onSelect', + propType: 'func', + }, + { + description: `面板模式变化时的回调␊ + @param {String} mode 对应面板模式 date month year`, + name: 'onModeChange', + propType: 'func', + }, + { + description: `展现的月份变化时的回调␊ + @param {Object} value 显示的月份 (moment 对象)␊ + @param {String} reason 触发月份改变原因`, + name: 'onVisibleMonthChange', + propType: 'func', + }, + { + description: '自定义样式类', + name: 'className', + propType: 'string', + }, + { + defaultValue: Function {}, + description: `自定义日期渲染函数␊ + @param {Object} value 日期值(moment对象)␊ + @returns {ReactNode}`, + name: 'dateCellRender', + propType: 'func', + }, + { + description: `自定义月份渲染函数␊ + @param {Object} calendarDate 对应 Calendar 返回的自定义日期对象␊ + @returns {ReactNode}`, + name: 'monthCellRender', + propType: 'func', + }, + { + name: 'yearCellRender', + propType: 'func', + }, + { + description: '年份范围,[START_YEAR, END_YEAR] (只在shape 为 ‘card’, \'fullscreen\' 下生效)', + name: 'yearRange', + propType: { + type: 'arrayOf', + value: 'number', + }, + }, + { + description: `不可选择的日期␊ + @param {Object} calendarDate 对应 Calendar 返回的自定义日期对象␊ + @param {String} view 当前视图类型,year: 年, month: 月, date: 日␊ + @returns {Boolean}`, + name: 'disabledDate', + propType: 'func', + }, + { + description: '国际化配置', + name: 'locale', + propType: 'object', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Card', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Card', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + name: 'rtl', + propType: 'bool', + }, + { + description: '卡片的上的图片 / 视频', + name: 'media', + propType: 'node', + }, + { + description: '卡片的标题', + name: 'title', + propType: 'node', + }, + { + description: '卡片的副标题', + name: 'subTitle', + propType: 'node', + }, + { + description: '卡片操作组,位置在卡片底部', + name: 'actions', + propType: 'node', + }, + { + defaultValue: true, + description: '是否显示标题的项目符号', + name: 'showTitleBullet', + propType: 'bool', + }, + { + defaultValue: true, + description: '是否展示头部的分隔线', + name: 'showHeadDivider', + propType: 'bool', + }, + { + defaultValue: 120, + description: '内容区域的固定高度', + name: 'contentHeight', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'number', + ], + }, + }, + { + description: '标题区域的用户自定义内容', + name: 'extra', + propType: 'node', + }, + { + defaultValue: false, + description: '是否开启自由模式,开启后card 将使用子组件配合使用, 设置此项后 title, subtitle, 等等属性都将失效', + name: 'free', + propType: 'bool', + }, + { + name: 'className', + propType: 'string', + }, + { + name: 'children', + propType: 'node', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Cascader', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Cascader', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + defaultValue: false, + name: 'rtl', + propType: 'bool', + }, + { + defaultValue: false, + name: 'pure', + propType: 'bool', + }, + { + name: 'className', + propType: 'string', + }, + { + defaultValue: [], + description: '数据源,结构可参考下方说明', + name: 'dataSource', + propType: { + type: 'arrayOf', + value: 'object', + }, + }, + { + defaultValue: null, + description: '(非受控)默认值', + name: 'defaultValue', + propType: { + type: 'oneOfType', + value: [ + 'string', + { + type: 'arrayOf', + value: 'string', + }, + ], + }, + }, + { + description: '(受控)当前值', + name: 'value', + propType: { + type: 'oneOfType', + value: [ + 'string', + { + type: 'arrayOf', + value: 'string', + }, + ], + }, + }, + { + description: `选中值改变时触发的回调函数␊ + @param {String|Array} value 选中的值,单选时返回单个值,多选时返回数组␊ + @param {Object|Array} data 选中的数据,包括 value 和 label,单选时返回单个值,多选时返回数组,父子节点选中关联时,同时选中,只返回父节点␊ + @param {Object} extra 额外参数␊ + @param {Array} extra.selectedPath 单选时选中的数据的路径␊ + @param {Boolean} extra.checked 多选时当前的操作是选中还是取消选中␊ + @param {Object} extra.currentData 多选时当前操作的数据␊ + @param {Array} extra.checkedData 多选时所有被选中的数据␊ + @param {Array} extra.indeterminateData 多选时半选的数据`, + name: 'onChange', + propType: 'func', + }, + { + name: 'onSelect', + propType: 'func', + }, + { + description: '(非受控)默认展开值,如果不设置,组件内部会根据 defaultValue/value 进行自动设置', + name: 'defaultExpandedValue', + propType: { + type: 'arrayOf', + value: 'string', + }, + }, + { + description: '(受控)当前展开值', + name: 'expandedValue', + propType: { + type: 'arrayOf', + value: 'string', + }, + }, + { + defaultValue: 'click', + description: '展开触发的方式', + name: 'expandTriggerType', + propType: { + type: 'oneOf', + value: [ + 'click', + 'hover', + ], + }, + }, + { + description: `展开时触发的回调函数␊ + @param {Array} expandedValue 各列展开值的数组`, + name: 'onExpand', + propType: 'func', + }, + { + defaultValue: false, + description: '是否开启虚拟滚动', + name: 'useVirtual', + propType: 'bool', + }, + { + defaultValue: false, + description: '是否多选', + name: 'multiple', + propType: 'bool', + }, + { + defaultValue: false, + description: '单选时是否只能选中叶子节点', + name: 'canOnlySelectLeaf', + propType: 'bool', + }, + { + defaultValue: false, + description: '多选时是否只能选中叶子节点', + name: 'canOnlyCheckLeaf', + propType: 'bool', + }, + { + defaultValue: false, + description: '父子节点是否选中不关联', + name: 'checkStrictly', + propType: 'bool', + }, + { + description: '每列列表样式对象', + name: 'listStyle', + propType: 'object', + }, + { + description: '每列列表类名', + name: 'listClassName', + propType: 'string', + }, + { + defaultValue: Function {}, + description: `每列列表项渲染函数␊ + @param {Object} data 数据␊ + @return {ReactNode} 列表项内容`, + name: 'itemRender', + propType: 'func', + }, + { + description: `异步加载数据函数␊ + @param {Object} data 当前点击异步加载的数据␊ + @param {Object} source 当前点击数据,source是原始对象`, + name: 'loadData', + propType: 'func', + }, + { + name: 'searchValue', + propType: 'string', + }, + { + name: 'onBlur', + propType: 'func', + }, + { + name: 'filteredPaths', + propType: 'array', + }, + { + name: 'filteredListStyle', + propType: 'object', + }, + { + name: 'resultRender', + propType: 'func', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'CascaderSelect', + docUrl: '', + npm: { + destructuring: false, + exportName: 'CascaderSelect', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + defaultValue: false, + name: 'pure', + propType: 'bool', + }, + { + name: 'className', + propType: 'string', + }, + { + defaultValue: 'medium', + description: '选择框大小', + name: 'size', + propType: { + type: 'oneOf', + value: [ + 'small', + 'medium', + 'large', + ], + }, + }, + { + description: '选择框占位符', + name: 'placeholder', + propType: 'string', + }, + { + defaultValue: false, + description: '是否禁用', + name: 'disabled', + propType: 'bool', + }, + { + defaultValue: true, + description: '是否有下拉箭头', + name: 'hasArrow', + propType: 'bool', + }, + { + defaultValue: true, + description: '是否有边框', + name: 'hasBorder', + propType: 'bool', + }, + { + defaultValue: false, + description: '是否有清除按钮', + name: 'hasClear', + propType: 'bool', + }, + { + description: '自定义内联 label', + name: 'label', + propType: 'node', + }, + { + description: '是否只读,只读模式下可以展开弹层但不能选', + name: 'readOnly', + propType: 'bool', + }, + { + defaultValue: [], + description: '数据源,结构可参考下方说明', + name: 'dataSource', + propType: { + type: 'arrayOf', + value: 'object', + }, + }, + { + defaultValue: null, + description: '(非受控)默认值', + name: 'defaultValue', + propType: { + type: 'oneOfType', + value: [ + 'string', + { + type: 'arrayOf', + value: 'string', + }, + ], + }, + }, + { + description: '(受控)当前值', + name: 'value', + propType: { + type: 'oneOfType', + value: [ + 'string', + { + type: 'arrayOf', + value: 'string', + }, + ], + }, + }, + { + description: `选中值改变时触发的回调函数␊ + @param {String|Array} value 选中的值,单选时返回单个值,多选时返回数组␊ + @param {Object|Array} data 选中的数据,包括 value 和 label,单选时返回单个值,多选时返回数组,父子节点选中关联时,同时选中,只返回父节点␊ + @param {Object} extra 额外参数␊ + @param {Array} extra.selectedPath 单选时选中的数据的路径␊ + @param {Boolean} extra.checked 多选时当前的操作是选中还是取消选中␊ + @param {Object} extra.currentData 多选时当前操作的数据␊ + @param {Array} extra.checkedData 多选时所有被选中的数据␊ + @param {Array} extra.indeterminateData 多选时半选的数据`, + name: 'onChange', + propType: 'func', + }, + { + description: '默认展开值,如果不设置,组件内部会根据 defaultValue/value 进行自动设置', + name: 'defaultExpandedValue', + propType: { + type: 'arrayOf', + value: 'string', + }, + }, + { + defaultValue: 'click', + description: '展开触发的方式', + name: 'expandTriggerType', + propType: { + type: 'oneOf', + value: [ + 'click', + 'hover', + ], + }, + }, + { + defaultValue: Function {}, + name: 'onExpand', + propType: 'func', + }, + { + defaultValue: false, + description: '是否开启虚拟滚动', + name: 'useVirtual', + propType: 'bool', + }, + { + defaultValue: false, + description: '是否多选', + name: 'multiple', + propType: 'bool', + }, + { + defaultValue: false, + description: '是否选中即发生改变, 该属性仅在单选模式下有效', + name: 'changeOnSelect', + propType: 'bool', + }, + { + defaultValue: false, + description: '是否只能勾选叶子项的checkbox,该属性仅在多选模式下有效', + name: 'canOnlyCheckLeaf', + propType: 'bool', + }, + { + defaultValue: false, + description: '父子节点是否选中不关联', + name: 'checkStrictly', + propType: 'bool', + }, + { + description: '每列列表样式对象', + name: 'listStyle', + propType: 'object', + }, + { + description: '每列列表类名', + name: 'listClassName', + propType: 'string', + }, + { + description: `选择框单选时展示结果的自定义渲染函数␊ + @param {Array} label 选中路径的文本数组␊ + @return {ReactNode} 渲染在选择框中的内容␊ + @default 单选时:labelPath => labelPath.join(' / ');多选时:labelPath => labelPath[labelPath.length - 1]`, + name: 'displayRender', + propType: 'func', + }, + { + description: `渲染 item 内容的方法␊ + @param {Object} item 渲染节点的item␊ + @return {ReactNode} item node`, + name: 'itemRender', + propType: 'func', + }, + { + defaultValue: false, + description: '是否显示搜索框', + name: 'showSearch', + propType: 'bool', + }, + { + defaultValue: Function {}, + description: `自定义搜索函数␊ + @param {String} searchValue 搜索的关键字␊ + @param {Array} path 节点路径␊ + @return {Boolean} 是否匹配␊ + @default 根据路径所有节点的文本值模糊匹配`, + name: 'filter', + propType: 'func', + }, + { + description: `搜索结果自定义渲染函数␊ + @param {String} searchValue 搜索的关键字␊ + @param {Array} path 匹配到的节点路径␊ + @return {ReactNode} 渲染的内容␊ + @default 按照节点文本 a / b / c 的模式渲染`, + name: 'resultRender', + propType: 'func', + }, + { + defaultValue: true, + description: '搜索结果列表是否和选择框等宽', + name: 'resultAutoWidth', + propType: 'bool', + }, + { + defaultValue: 'Not Found', + description: '无数据时显示内容', + name: 'notFoundContent', + propType: 'node', + }, + { + description: `异步加载数据函数␊ + @param {Object} data 当前点击异步加载的数据`, + name: 'loadData', + propType: 'func', + }, + { + description: '自定义下拉框头部', + name: 'header', + propType: 'node', + }, + { + description: '自定义下拉框底部', + name: 'footer', + propType: 'node', + }, + { + defaultValue: false, + description: '初始下拉框是否显示', + name: 'defaultVisible', + propType: 'bool', + }, + { + description: '当前下拉框是否显示', + name: 'visible', + propType: 'bool', + }, + { + defaultValue: Function {}, + description: `下拉框显示或关闭时触发事件的回调函数␊ + @param {Boolean} visible 是否显示␊ + @param {String} type 触发显示关闭的操作类型, fromTrigger 表示由trigger的点击触发; docClick 表示由document的点击触发`, + name: 'onVisibleChange', + propType: 'func', + }, + { + description: '下拉框自定义样式对象', + name: 'popupStyle', + propType: 'object', + }, + { + description: '下拉框样式自定义类名', + name: 'popupClassName', + propType: 'string', + }, + { + description: '下拉框挂载的容器节点', + name: 'popupContainer', + propType: 'any', + }, + { + defaultValue: undefined, + description: '透传到 Popup 的属性对象', + name: 'popupProps', + propType: 'object', + }, + { + description: '是否跟随滚动', + name: 'followTrigger', + propType: 'bool', + }, + { + description: '是否为预览态', + name: 'isPreview', + propType: 'bool', + }, + { + description: `预览态模式下渲染的内容␊ + @param {Array} value 选择值 { label: , value:}`, + name: 'renderPreview', + propType: 'func', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Checkbox', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Checkbox', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + name: 'rtl', + propType: 'bool', + }, + { + description: '自定义类名', + name: 'className', + propType: 'string', + }, + { + description: 'checkbox id, 挂载在input上', + name: 'id', + propType: 'string', + }, + { + description: '自定义内敛样式', + name: 'style', + propType: 'object', + }, + { + description: '选中状态', + name: 'checked', + propType: 'bool', + }, + { + defaultValue: false, + description: '默认选中状态', + name: 'defaultChecked', + propType: 'bool', + }, + { + description: '禁用', + name: 'disabled', + propType: 'bool', + }, + { + description: '通过属性配置label,', + name: 'label', + propType: 'node', + }, + { + description: 'Checkbox 的中间状态,只会影响到 Checkbox 的样式,并不影响其 checked 属性', + name: 'indeterminate', + propType: 'bool', + }, + { + defaultValue: false, + description: 'Checkbox 的默认中间态,只会影响到 Checkbox 的样式,并不影响其 checked 属性', + name: 'defaultIndeterminate', + propType: 'bool', + }, + { + description: `状态变化时触发的事件␊ + @param {Boolean} checked 是否选中␊ + @param {Event} e Dom 事件对象`, + name: 'onChange', + propType: 'func', + }, + { + description: `鼠标进入enter事件␊ + @param {Event} e Dom 事件对象`, + name: 'onMouseEnter', + propType: 'func', + }, + { + description: `鼠标离开Leave事件␊ + @param {Event} e Dom 事件对象`, + name: 'onMouseLeave', + propType: 'func', + }, + { + description: 'checkbox 的value', + name: 'value', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'number', + ], + }, + }, + { + description: 'name', + name: 'name', + propType: 'string', + }, + { + defaultValue: false, + description: '是否为预览态', + name: 'isPreview', + propType: 'bool', + }, + { + description: `预览态模式下渲染的内容␊ + @param {number} value 评分值`, + name: 'renderPreview', + propType: 'func', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Collapse', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Collapse', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + description: '样式前缀', + name: 'prefix', + propType: 'string', + }, + { + description: '组件接受行内样式', + name: 'style', + propType: 'object', + }, + { + description: '使用数据模型构建', + name: 'dataSource', + propType: 'array', + }, + { + description: '默认展开keys', + name: 'defaultExpandedKeys', + propType: 'array', + }, + { + description: '受控展开keys', + name: 'expandedKeys', + propType: 'array', + }, + { + description: '展开状态发升变化时候的回调', + name: 'onExpand', + propType: 'func', + }, + { + description: '所有禁用', + name: 'disabled', + propType: 'bool', + }, + { + description: '扩展class', + name: 'className', + propType: 'string', + }, + { + defaultValue: false, + description: '手风琴模式,一次只能打开一个', + name: 'accordion', + propType: 'bool', + }, + { + name: 'children', + propType: 'node', + }, + { + name: 'id', + propType: 'string', + }, + { + name: 'rtl', + propType: 'bool', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'ConfigProvider', + docUrl: '', + npm: { + destructuring: false, + exportName: 'ConfigProvider', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + description: '样式类名的品牌前缀', + name: 'prefix', + propType: 'string', + }, + { + description: '国际化文案对象,属性为组件的 displayName', + name: 'locale', + propType: 'object', + }, + { + defaultValue: false, + description: `是否开启错误捕捉 errorBoundary␊ + 如需自定义参数,请传入对象 对象接受参数列表如下:␊ + ␊ + fallbackUI `Function(error?: {}, errorInfo?: {}) => Element` 捕获错误后的展示␊ + afterCatch `Function(error?: {}, errorInfo?: {})` 捕获错误后的行为, 比如埋点上传`, + name: 'errorBoundary', + propType: { + type: 'oneOfType', + value: [ + 'bool', + 'object', + ], + }, + }, + { + description: '是否开启 Pure Render 模式,会提高性能,但是也会带来副作用', + name: 'pure', + propType: 'bool', + }, + { + defaultValue: true, + description: '是否在开发模式下显示组件属性被废弃的 warning 提示', + name: 'warning', + propType: 'bool', + }, + { + description: '是否开启 rtl 模式', + name: 'rtl', + propType: 'bool', + }, + { + description: '设备类型,针对不同的设备类型组件做出对应的响应式变化', + name: 'device', + propType: { + type: 'oneOf', + value: [ + 'tablet', + 'desktop', + 'phone', + ], + }, + }, + { + description: '组件树', + name: 'children', + propType: 'any', + }, + { + description: '指定浮层渲染的父节点, 可以为节点id的字符串,也可以返回节点的函数', + name: 'popupContainer', + propType: 'any', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'DatePicker', + docUrl: '', + npm: { + destructuring: false, + exportName: 'DatePicker', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + defaultValue: false, + name: 'rtl', + propType: 'bool', + }, + { + description: '输入框内置标签', + name: 'label', + propType: 'node', + }, + { + description: '输入框状态', + name: 'state', + propType: { + type: 'oneOf', + value: [ + 'success', + 'loading', + 'error', + ], + }, + }, + { + description: '输入提示', + name: 'placeholder', + propType: 'string', + }, + { + description: `默认展现的月␊ + @return {MomentObject} 返回包含指定月份的 moment 对象实例`, + name: 'defaultVisibleMonth', + propType: 'func', + }, + { + name: 'onVisibleMonthChange', + propType: 'func', + }, + { + description: '日期值(受控)moment 对象', + name: 'value', + propType: 'custom', + }, + { + description: '初始日期值,moment 对象', + name: 'defaultValue', + propType: 'custom', + }, + { + defaultValue: 'YYYY-MM-DD', + description: '日期值的格式(用于限定用户输入和展示)', + name: 'format', + propType: 'string', + }, + { + defaultValue: false, + description: '是否使用时间控件,传入 TimePicker 的属性 { defaultValue, format, ... }', + name: 'showTime', + propType: { + type: 'oneOfType', + value: [ + 'object', + 'bool', + ], + }, + }, + { + defaultValue: false, + description: '每次选择日期时是否重置时间(仅在 showTime 开启时有效)', + name: 'resetTime', + propType: 'bool', + }, + { + defaultValue: Function {}, + description: `禁用日期函数␊ + @param {MomentObject} 日期值␊ + @param {String} view 当前视图类型,year: 年, month: 月, date: 日␊ + @return {Boolean} 是否禁用`, + name: 'disabledDate', + propType: 'func', + }, + { + defaultValue: Function {}, + description: `自定义面板页脚␊ + @return {Node} 自定义的面板页脚组件`, + name: 'footerRender', + propType: 'func', + }, + { + description: `日期值改变时的回调␊ + @param {MomentObject|String} value 日期值`, + name: 'onChange', + propType: 'func', + }, + { + description: `点击确认按钮时的回调␊ + @return {MomentObject|String} 日期值`, + name: 'onOk', + propType: 'func', + }, + { + defaultValue: 'medium', + description: '输入框尺寸', + name: 'size', + propType: { + type: 'oneOf', + value: [ + 'small', + 'medium', + 'large', + ], + }, + }, + { + description: '是否禁用', + name: 'disabled', + propType: 'bool', + }, + { + defaultValue: true, + description: '是否显示清空按钮', + name: 'hasClear', + propType: 'bool', + }, + { + description: '弹层显示状态', + name: 'visible', + propType: 'bool', + }, + { + defaultValue: false, + description: '弹层默认是否显示', + name: 'defaultVisible', + propType: 'bool', + }, + { + description: `弹层展示状态变化时的回调␊ + @param {Boolean} visible 弹层是否显示␊ + @param {String} type 触发弹层显示和隐藏的来源 calendarSelect 表示由日期表盘的选择触发; okBtnClick 表示由确认按钮触发; fromTrigger 表示由trigger的点击触发; docClick 表示由document的点击触发`, + name: 'onVisibleChange', + propType: 'func', + }, + { + defaultValue: 'click', + description: '弹层触发方式', + name: 'popupTriggerType', + propType: { + type: 'oneOf', + value: [ + 'click', + 'hover', + ], + }, + }, + { + defaultValue: 'tl tl', + description: '弹层对齐方式,具体含义见 OverLay文档', + name: 'popupAlign', + propType: 'string', + }, + { + description: `弹层容器␊ + @param {Element} target 目标元素␊ + @return {Element} 弹层的容器元素`, + name: 'popupContainer', + propType: 'any', + }, + { + description: '弹层自定义样式', + name: 'popupStyle', + propType: 'object', + }, + { + description: '弹层自定义样式类', + name: 'popupClassName', + propType: 'string', + }, + { + description: '弹层其他属性', + name: 'popupProps', + propType: 'object', + }, + { + description: '是否跟随滚动', + name: 'followTrigger', + propType: 'bool', + }, + { + description: '输入框其他属性', + name: 'inputProps', + propType: 'object', + }, + { + description: `自定义日期渲染函数␊ + @param {Object} value 日期值(moment对象)␊ + @returns {ReactNode}`, + name: 'dateCellRender', + propType: 'func', + }, + { + description: `自定义月份渲染函数␊ + @param {Object} calendarDate 对应 Calendar 返回的自定义日期对象␊ + @returns {ReactNode}`, + name: 'monthCellRender', + propType: 'func', + }, + { + name: 'yearCellRender', + propType: 'func', + }, + { + description: '日期输入框的 aria-label 属性', + name: 'dateInputAriaLabel', + propType: 'string', + }, + { + description: '时间输入框的 aria-label 属性', + name: 'timeInputAriaLabel', + propType: 'string', + }, + { + description: '是否为预览态', + name: 'isPreview', + propType: 'bool', + }, + { + description: `预览态模式下渲染的内容␊ + @param {MomentObject} value 日期`, + name: 'renderPreview', + propType: 'func', + }, + { + name: 'locale', + propType: 'object', + }, + { + name: 'className', + propType: 'string', + }, + { + name: 'name', + propType: 'string', + }, + { + name: 'popupComponent', + propType: 'elementType', + }, + { + name: 'popupContent', + propType: 'node', + }, + { + name: 'disableChangeMode', + propType: 'bool', + }, + { + name: 'yearRange', + propType: { + type: 'arrayOf', + value: 'number', + }, + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Dialog', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Dialog', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + defaultValue: false, + name: 'pure', + propType: 'bool', + }, + { + name: 'rtl', + propType: 'bool', + }, + { + name: 'className', + propType: 'string', + }, + { + defaultValue: false, + description: '是否显示', + name: 'visible', + propType: 'bool', + }, + { + description: '标题', + name: 'title', + propType: 'node', + }, + { + description: '内容', + name: 'children', + propType: 'node', + }, + { + description: `底部内容,设置为 false,则不进行显示␊ + @default [, ]`, + name: 'footer', + propType: { + type: 'oneOfType', + value: [ + 'bool', + 'node', + ], + }, + }, + { + defaultValue: 'right', + description: '底部按钮的对齐方式', + name: 'footerAlign', + propType: { + type: 'oneOf', + value: [ + 'left', + 'center', + 'right', + ], + }, + }, + { + defaultValue: [ + 'ok', + 'cancel', + ], + description: `指定确定按钮和取消按钮是否存在以及如何排列,

**可选值**:␊ + ['ok', 'cancel'](确认取消按钮同时存在,确认按钮在左)␊ + ['cancel', 'ok'](确认取消按钮同时存在,确认按钮在右)␊ + ['ok'](只存在确认按钮)␊ + ['cancel'](只存在取消按钮)`, + name: 'footerActions', + propType: 'array', + }, + { + defaultValue: Function {}, + description: `在点击确定按钮时触发的回调函数␊ + @param {Object} event 点击事件对象`, + name: 'onOk', + propType: 'func', + }, + { + defaultValue: Function {}, + description: `在点击取消按钮时触发的回调函数␊ + @param {Object} event 点击事件对象`, + name: 'onCancel', + propType: 'func', + }, + { + defaultValue: undefined, + description: '应用于确定按钮的属性对象', + name: 'okProps', + propType: 'object', + }, + { + defaultValue: undefined, + description: '应用于取消按钮的属性对象', + name: 'cancelProps', + propType: 'object', + }, + { + defaultValue: 'esc,close', + description: `控制对话框关闭的方式,值可以为字符串或者布尔值,其中字符串是由以下值组成:␊ + **close** 表示点击关闭按钮可以关闭对话框␊ + **mask** 表示点击遮罩区域可以关闭对话框␊ + **esc** 表示按下 esc 键可以关闭对话框␊ + 如 'close' 或 'close,esc,mask'␊ + 如果设置为 true,则以上关闭方式全部生效␊ + 如果设置为 false,则以上关闭方式全部失效`, + name: 'closeable', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'bool', + ], + }, + }, + { + defaultValue: Function {}, + description: `对话框关闭时触发的回调函数␊ + @param {String} trigger 关闭触发行为的描述字符串␊ + @param {Object} event 关闭时事件对象`, + name: 'onClose', + propType: 'func', + }, + { + defaultValue: Function {}, + description: '对话框关闭后触发的回调函数, 如果有动画,则在动画结束后触发', + name: 'afterClose', + propType: 'func', + }, + { + defaultValue: true, + description: '是否显示遮罩', + name: 'hasMask', + propType: 'bool', + }, + { + description: `显示隐藏时动画的播放方式␊ + @property {String} in 进场动画␊ + @property {String} out 出场动画`, + name: 'animation', + propType: { + type: 'oneOfType', + value: [ + 'object', + 'bool', + ], + }, + }, + { + defaultValue: false, + description: '对话框弹出时是否自动获得焦点', + name: 'autoFocus', + propType: 'bool', + }, + { + defaultValue: 'cc cc', + description: '对话框对齐方式, 具体见Overlay文档', + name: 'align', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'bool', + ], + }, + }, + { + defaultValue: false, + description: '当对话框高度超过浏览器视口高度时,是否显示所有内容而不是出现滚动条以保证对话框完整显示在浏览器视口内,该属性仅在对话框垂直水平居中时生效,即 align 被设置为 \'cc cc\' 时', + name: 'isFullScreen', + propType: 'bool', + }, + { + defaultValue: false, + description: '是否在对话框重新渲染时及时更新对话框位置,一般用于对话框高度变化后依然能保证原来的对齐方式', + name: 'shouldUpdatePosition', + propType: 'bool', + }, + { + defaultValue: 40, + description: '对话框距离浏览器顶部和底部的最小间距,align 被设置为 \'cc cc\' 并且 isFullScreen 被设置为 true 时不生效', + name: 'minMargin', + propType: 'number', + }, + { + defaultValue: undefined, + description: '透传到弹层组件的属性对象', + name: 'overlayProps', + propType: 'object', + }, + { + description: `自定义国际化文案对象␊ + @property {String} ok 确认按钮文案␊ + @property {String} cancel 取消按钮文案`, + name: 'locale', + propType: 'object', + }, + { + description: '对话框的高度样式属性', + name: 'height', + propType: 'string', + }, + { + name: 'popupContainer', + propType: 'any', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Dropdown', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Dropdown', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + defaultValue: false, + name: 'pure', + propType: 'bool', + }, + { + name: 'rtl', + propType: 'bool', + }, + { + name: 'className', + propType: 'string', + }, + { + description: '弹层内容', + name: 'children', + propType: 'node', + }, + { + description: '弹层当前是否显示', + name: 'visible', + propType: 'bool', + }, + { + defaultValue: false, + description: '弹层默认是否显示', + name: 'defaultVisible', + propType: 'bool', + }, + { + description: `弹层显示或隐藏时触发的回调函数␊ + @param {Boolean} visible 弹层是否显示␊ + @param {String} type 触发弹层显示或隐藏的来源 fromContent 表示由Dropdown内容触发; fromTrigger 表示由trigger的点击触发; docClick 表示由document的点击触发`, + name: 'onVisibleChange', + propType: 'func', + }, + { + description: '触发弹层显示或者隐藏的元素', + name: 'trigger', + propType: 'node', + }, + { + defaultValue: 'hover', + description: '触发弹层显示或隐藏的操作类型,可以是 \'click\',\'hover\',或者它们组成的数组,如 [\'hover\', \'click\']', + name: 'triggerType', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'array', + ], + }, + }, + { + defaultValue: false, + description: '设置此属性,弹层无法显示或隐藏', + name: 'disabled', + propType: 'bool', + }, + { + defaultValue: 'tl bl', + description: '弹层相对于触发元素的定位, 详见 Overlay 的定位部分', + name: 'align', + propType: 'string', + }, + { + defaultValue: [ + 0, + 0, + ], + description: `弹层相对于trigger的定位的微调, 接收数组[hoz, ver], 表示弹层在 left / top 上的增量␊ + e.g. [100, 100] 表示往右(RTL 模式下是往左) 、下分布偏移100px`, + name: 'offset', + propType: 'array', + }, + { + defaultValue: 200, + description: '弹层显示或隐藏的延时时间(以毫秒为单位),在 triggerType 被设置为 hover 时生效', + name: 'delay', + propType: 'number', + }, + { + description: '弹层打开时是否让其中的元素自动获取焦点', + name: 'autoFocus', + propType: 'bool', + }, + { + defaultValue: false, + description: '是否显示遮罩', + name: 'hasMask', + propType: 'bool', + }, + { + defaultValue: false, + description: '隐藏时是否保留子节点', + name: 'cache', + propType: 'bool', + }, + { + description: `配置动画的播放方式,支持 { in: 'enter-class', out: 'leave-class' } 的对象参数,如果设置为 false,则不播放动画␊ + @default { in: 'expandInDown', out: 'expandOutUp' }`, + name: 'animation', + propType: { + type: 'oneOfType', + value: [ + 'object', + 'bool', + ], + }, + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Drawer', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Drawer', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + name: 'pure', + propType: 'bool', + }, + { + name: 'rtl', + propType: 'bool', + }, + { + defaultValue: null, + name: 'trigger', + propType: 'element', + }, + { + defaultValue: 'click', + name: 'triggerType', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'array', + ], + }, + }, + { + description: '宽度,仅在 placement是 left right 的时候生效', + name: 'width', + propType: { + type: 'oneOfType', + value: [ + 'number', + 'string', + ], + }, + }, + { + description: '高度,仅在 placement是 top bottom 的时候生效', + name: 'height', + propType: { + type: 'oneOfType', + value: [ + 'number', + 'string', + ], + }, + }, + { + defaultValue: true, + description: `控制对话框关闭的方式,值可以为字符串或者布尔值,其中字符串是由以下值组成:␊ + **close** 表示点击关闭按钮可以关闭对话框␊ + **mask** 表示点击遮罩区域可以关闭对话框␊ + **esc** 表示按下 esc 键可以关闭对话框␊ + 如 'close' 或 'close,esc,mask'␊ + 如果设置为 true,则以上关闭方式全部生效␊ + 如果设置为 false,则以上关闭方式全部失效`, + name: 'closeable', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'bool', + ], + }, + }, + { + defaultValue: Function {}, + description: `对话框关闭时触发的回调函数␊ + @param {String} trigger 关闭触发行为的描述字符串␊ + @param {Object} event 关闭时事件对象`, + name: 'onClose', + propType: 'func', + }, + { + defaultValue: 'right', + description: '位于页面的位置', + name: 'placement', + propType: { + type: 'oneOf', + value: [ + 'top', + 'right', + 'bottom', + 'left', + ], + }, + }, + { + description: '标题', + name: 'title', + propType: 'node', + }, + { + description: 'header上的样式', + name: 'headerStyle', + propType: 'object', + }, + { + description: 'body上的样式', + name: 'bodyStyle', + propType: 'object', + }, + { + description: '是否显示', + name: 'visible', + propType: 'bool', + }, + { + defaultValue: true, + description: '是否显示遮罩', + name: 'hasMask', + propType: 'bool', + }, + { + name: 'onVisibleChange', + propType: 'func', + }, + { + description: `显示隐藏时动画的播放方式␊ + @property {String} in 进场动画␊ + @property {String} out 出场动画`, + name: 'animation', + propType: { + type: 'oneOfType', + value: [ + 'object', + 'bool', + ], + }, + }, + { + name: 'locale', + propType: 'object', + }, + { + name: 'popupContainer', + propType: 'any', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Form', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Form', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + description: '样式前缀', + name: 'prefix', + propType: 'string', + }, + { + description: '内联表单', + name: 'inline', + propType: 'bool', + }, + { + defaultValue: 'medium', + description: `单个 Item 的 size 自定义,优先级高于 Form 的 size, 并且当组件与 Item 一起使用时,组件自身设置 size 属性无效。␊ + @enumdesc 大, 中, 小`, + name: 'size', + propType: { + type: 'oneOf', + value: [ + 'large', + 'medium', + 'small', + ], + }, + }, + { + description: '单个 Item 中表单类组件宽度是否是100%', + name: 'fullWidth', + propType: 'bool', + }, + { + defaultValue: 'left', + description: `标签的位置␊ + @enumdesc 上, 左, 内`, + name: 'labelAlign', + propType: { + type: 'oneOf', + value: [ + 'top', + 'left', + 'inset', + ], + }, + }, + { + description: `标签的左右对齐方式␊ + @enumdesc 左, 右`, + name: 'labelTextAlign', + propType: { + type: 'oneOf', + value: [ + 'left', + 'right', + ], + }, + }, + { + description: 'field 实例, 传 false 会禁用 field', + name: 'field', + propType: 'any', + }, + { + description: '保存 Form 自动生成的 field 对象', + name: 'saveField', + propType: 'func', + }, + { + description: '控制第一级 Item 的 labelCol', + name: 'labelCol', + propType: 'object', + }, + { + description: '控制第一级 Item 的 wrapperCol', + name: 'wrapperCol', + propType: 'object', + }, + { + defaultValue: undefined, + description: 'form内有 `htmlType="submit"` 的元素的时候会触发', + name: 'onSubmit', + propType: 'func', + }, + { + description: '子元素', + name: 'children', + propType: 'any', + }, + { + description: '扩展class', + name: 'className', + propType: 'string', + }, + { + description: '自定义内联样式', + name: 'style', + propType: 'object', + }, + { + description: '表单数值', + name: 'value', + propType: 'object', + }, + { + description: `表单变化回调␊ + @param {Object} values 表单数据␊ + @param {Object} item 详细␊ + @param {String} item.name 变化的组件名␊ + @param {String} item.value 变化的数据␊ + @param {Object} item.field field 实例`, + name: 'onChange', + propType: 'func', + }, + { + defaultValue: 'form', + description: '设置标签类型', + name: 'component', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'func', + ], + }, + }, + { + name: 'fieldOptions', + propType: 'object', + }, + { + name: 'rtl', + propType: 'bool', + }, + { + defaultValue: 'desktop', + description: '预设屏幕宽度', + name: 'device', + propType: { + type: 'oneOf', + value: [ + 'phone', + 'tablet', + 'desktop', + ], + }, + }, + { + description: '是否开启内置的响应式布局 (使用ResponsiveGrid)', + name: 'responsive', + propType: 'bool', + }, + { + description: '是否开启预览态', + name: 'isPreview', + propType: 'bool', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Icon', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Icon', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + description: '指定显示哪种图标', + name: 'type', + propType: 'string', + }, + { + name: 'children', + propType: 'node', + }, + { + defaultValue: 'medium', + description: `指定图标大小␊ +
**可选值**
xxs, xs, small, medium, large, xl, xxl, xxxl, inherit`, + name: 'size', + propType: { + type: 'oneOfType', + value: [ + { + type: 'oneOf', + value: [ + 'xxs', + 'xs', + 'small', + 'medium', + 'large', + 'xl', + 'xxl', + 'xxxl', + 'inherit', + ], + }, + 'number', + ], + }, + }, + { + name: 'className', + propType: 'string', + }, + { + name: 'style', + propType: 'object', + }, + { + defaultValue: 'next-', + name: 'prefix', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Input', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Input', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + description: 'label', + name: 'label', + propType: 'node', + }, + { + description: '是否出现clear按钮', + name: 'hasClear', + propType: 'bool', + }, + { + defaultValue: true, + description: '是否有边框', + name: 'hasBorder', + propType: 'bool', + }, + { + description: `状态␊ + @enumdesc 错误, 校验中, 成功, 警告`, + name: 'state', + propType: { + type: 'oneOf', + value: [ + 'error', + 'loading', + 'success', + 'warning', + ], + }, + }, + { + defaultValue: 'medium', + description: `尺寸␊ + @enumdesc 小, 中, 大`, + name: 'size', + propType: { + type: 'oneOf', + value: [ + 'small', + 'medium', + 'large', + ], + }, + }, + { + description: '按下回车的回调', + name: 'onPressEnter', + propType: 'func', + }, + { + name: 'onClear', + propType: 'func', + }, + { + description: '原生type', + name: 'htmlType', + propType: 'string', + }, + { + name: 'htmlSize', + propType: 'string', + }, + { + description: '水印 (Icon的type类型,和hasClear占用一个地方)', + name: 'hint', + propType: 'string', + }, + { + description: '文字前附加内容', + name: 'innerBefore', + propType: 'node', + }, + { + description: '文字后附加内容', + name: 'innerAfter', + propType: 'node', + }, + { + description: '输入框前附加内容', + name: 'addonBefore', + propType: 'node', + }, + { + description: '输入框后附加内容', + name: 'addonAfter', + propType: 'node', + }, + { + description: '输入框前附加文字', + name: 'addonTextBefore', + propType: 'node', + }, + { + description: '输入框后附加文字', + name: 'addonTextAfter', + propType: 'node', + }, + { + defaultValue: 'off', + description: '(原生input支持)', + name: 'autoComplete', + propType: 'string', + }, + { + description: '自动聚焦(原生input支持)', + name: 'autoFocus', + propType: 'bool', + }, + { + defaultValue: Function {}, + name: 'inputRender', + propType: 'func', + }, + { + name: 'extra', + propType: 'node', + }, + { + name: 'innerBeforeClassName', + propType: 'string', + }, + { + name: 'innerAfterClassName', + propType: 'string', + }, + { + defaultValue: false, + description: '是否为预览态', + name: 'isPreview', + propType: 'bool', + }, + { + description: `预览态模式下渲染的内容␊ + @param {number} value 评分值`, + name: 'renderPreview', + propType: 'func', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Loading', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Loading', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + description: '样式前缀', + name: 'prefix', + propType: 'string', + }, + { + description: '自定义内容', + name: 'tip', + propType: 'any', + }, + { + defaultValue: 'bottom', + description: `自定义内容位置␊ + @enumdesc 出现在动画右边, 出现在动画下面`, + name: 'tipAlign', + propType: { + type: 'oneOf', + value: [ + 'right', + 'bottom', + ], + }, + }, + { + defaultValue: true, + description: 'loading 状态, 默认 true', + name: 'visible', + propType: 'bool', + }, + { + name: 'onVisibleChange', + propType: 'func', + }, + { + description: '自定义class', + name: 'className', + propType: 'string', + }, + { + description: '自定义内联样式', + name: 'style', + propType: 'object', + }, + { + defaultValue: 'large', + description: `设置动画尺寸␊ + @description 仅仅对默认动画效果起作用␊ + @enumdesc 大号, 中号`, + name: 'size', + propType: { + type: 'oneOf', + value: [ + 'large', + 'medium', + ], + }, + }, + { + description: '自定义动画', + name: 'indicator', + propType: 'any', + }, + { + description: '动画颜色', + name: 'color', + propType: 'string', + }, + { + description: '全屏展示', + name: 'fullScreen', + propType: 'bool', + }, + { + description: '子元素', + name: 'children', + propType: 'any', + }, + { + defaultValue: true, + description: 'should loader be displayed inline', + name: 'inline', + propType: 'bool', + }, + { + name: 'rtl', + propType: 'bool', + }, + { + defaultValue: null, + name: 'animate', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Menu', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Menu', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + defaultValue: false, + name: 'pure', + propType: 'bool', + }, + { + name: 'rtl', + propType: 'bool', + }, + { + name: 'className', + propType: 'string', + }, + { + description: '菜单项和子菜单', + name: 'children', + propType: 'node', + }, + { + defaultValue: Function {}, + description: `点击菜单项触发的回调函数␊ + @param {String} key 点击的菜单项的 key 值␊ + @param {Object} item 点击的菜单项对象␊ + @param {Object} event 点击的事件对象`, + name: 'onItemClick', + propType: 'func', + }, + { + description: '当前打开的子菜单的 key 值', + name: 'openKeys', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'array', + ], + }, + }, + { + defaultValue: [], + description: '初始打开的子菜单的 key 值', + name: 'defaultOpenKeys', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'array', + ], + }, + }, + { + defaultValue: false, + description: '初始展开所有的子菜单,只在 mode 设置为 \'inline\' 以及 openMode 设置为 \'multiple\' 下生效,优先级高于 defaultOpenKeys', + name: 'defaultOpenAll', + propType: 'bool', + }, + { + defaultValue: Function {}, + description: `打开或关闭子菜单触发的回调函数␊ + @param {String} key 打开的所有子菜单的 key 值␊ + @param {Object} extra 额外参数␊ + @param {String} extra.key 当前操作子菜单的 key 值␊ + @param {Boolean} extra.open 是否是打开`, + name: 'onOpen', + propType: 'func', + }, + { + defaultValue: 'inline', + description: '子菜单打开的模式', + name: 'mode', + propType: { + type: 'oneOf', + value: [ + 'inline', + 'popup', + ], + }, + }, + { + defaultValue: 'click', + description: '子菜单打开的触发行为', + name: 'triggerType', + propType: { + type: 'oneOf', + value: [ + 'click', + 'hover', + ], + }, + }, + { + defaultValue: 'multiple', + description: '展开内连子菜单的模式,同时可以展开一个子菜单还是多个子菜单,该属性仅在 mode 为 inline 时生效', + name: 'openMode', + propType: { + type: 'oneOf', + value: [ + 'single', + 'multiple', + ], + }, + }, + { + defaultValue: 20, + description: '内连子菜单缩进距离', + name: 'inlineIndent', + propType: 'number', + }, + { + defaultValue: 'down', + name: 'inlineArrowDirection', + propType: { + type: 'oneOf', + value: [ + 'down', + 'right', + ], + }, + }, + { + defaultValue: false, + description: '是否自动让弹层的宽度和菜单项保持一致,如果弹层的宽度比菜单项小则和菜单项保持一致,如果宽度大于菜单项则不做处理', + name: 'popupAutoWidth', + propType: 'bool', + }, + { + defaultValue: 'follow', + description: '弹层的对齐方式', + name: 'popupAlign', + propType: { + type: 'oneOf', + value: [ + 'follow', + 'outside', + ], + }, + }, + { + defaultValue: undefined, + description: '弹层自定义 props', + name: 'popupProps', + propType: { + type: 'oneOfType', + value: [ + 'object', + 'func', + ], + }, + }, + { + description: '弹出子菜单自定义 className', + name: 'popupClassName', + propType: 'string', + }, + { + description: '弹出子菜单自定义 style', + name: 'popupStyle', + propType: 'object', + }, + { + description: '当前选中菜单项的 key 值', + name: 'selectedKeys', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'array', + ], + }, + }, + { + defaultValue: [], + description: '初始选中菜单项的 key 值', + name: 'defaultSelectedKeys', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'array', + ], + }, + }, + { + defaultValue: Function {}, + description: `选中或取消选中菜单项触发的回调函数␊ + @param {Array} selectedKeys 选中的所有菜单项的值␊ + @param {Object} item 选中或取消选中的菜单项␊ + @param {Object} extra 额外参数␊ + @param {Boolean} extra.select 是否是选中␊ + @param {Array} extra.key 菜单项的 key␊ + @param {Object} extra.label 菜单项的文本␊ + @param {Array} extra.keyPath 菜单项 key 的路径`, + name: 'onSelect', + propType: 'func', + }, + { + description: '选中模式,单选还是多选,默认无值,不可选', + name: 'selectMode', + propType: { + type: 'oneOf', + value: [ + 'single', + 'multiple', + ], + }, + }, + { + defaultValue: false, + description: '是否只能选择第一层菜单项(不能选择子菜单中的菜单项)', + name: 'shallowSelect', + propType: 'bool', + }, + { + defaultValue: true, + description: '是否显示选中图标,如果设置为 false 需配合配置平台设置选中时的背景色以示区分', + name: 'hasSelectedIcon', + propType: 'bool', + }, + { + defaultValue: true, + name: 'labelToggleChecked', + propType: 'bool', + }, + { + defaultValue: false, + description: `是否将选中图标居右,仅当 hasSelectedIcon 为true 时生效。␊ + 注意:SubMenu 上的选中图标一直居左,不受此API控制`, + name: 'isSelectIconRight', + propType: 'bool', + }, + { + defaultValue: 'ver', + description: '菜单第一层展示方向', + name: 'direction', + propType: { + type: 'oneOf', + value: [ + 'ver', + 'hoz', + ], + }, + }, + { + defaultValue: 'left', + description: '横向菜单条 item 和 footer 的对齐方向,在 direction 设置为 \'hoz\' 并且 header 存在时生效', + name: 'hozAlign', + propType: { + type: 'oneOf', + value: [ + 'left', + 'right', + ], + }, + }, + { + defaultValue: false, + description: '横向菜单模式下,是否维持在一行,即超出一行折叠成 SubMenu 显示, 仅在 direction=\'hoz\' mode=\'popup\' 时生效', + name: 'hozInLine', + propType: 'bool', + }, + { + name: 'renderMore', + propType: 'func', + }, + { + description: '自定义菜单头部', + name: 'header', + propType: 'node', + }, + { + description: '自定义菜单尾部', + name: 'footer', + propType: 'node', + }, + { + defaultValue: false, + description: '是否自动获得焦点', + name: 'autoFocus', + propType: 'bool', + }, + { + description: '当前获得焦点的子菜单或菜单项 key 值', + name: 'focusedKey', + propType: 'string', + }, + { + defaultValue: true, + name: 'focusable', + propType: 'bool', + }, + { + defaultValue: Function {}, + name: 'onItemFocus', + propType: 'func', + }, + { + name: 'onBlur', + propType: 'func', + }, + { + defaultValue: false, + description: '是否开启嵌入式模式,一般用于Layout的布局中,开启后没有默认背景、外层border、box-shadow,可以配合`` 自定义高度', + name: 'embeddable', + propType: 'bool', + }, + { + defaultValue: Function {}, + name: 'onItemKeyDown', + propType: 'func', + }, + { + defaultValue: true, + name: 'expandAnimation', + propType: 'bool', + }, + { + name: 'itemClassName', + propType: 'string', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'MenuButton', + docUrl: '', + npm: { + destructuring: false, + exportName: 'MenuButton', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + description: '按钮上的文本内容', + name: 'label', + propType: 'node', + }, + { + defaultValue: true, + description: '弹层是否与按钮宽度相同', + name: 'autoWidth', + propType: 'bool', + }, + { + defaultValue: 'click', + description: '弹层触发方式', + name: 'popupTriggerType', + propType: { + type: 'oneOf', + value: [ + 'click', + 'hover', + ], + }, + }, + { + description: '弹层容器', + name: 'popupContainer', + propType: 'any', + }, + { + description: '弹层展开状态', + name: 'visible', + propType: 'bool', + }, + { + description: '弹层默认是否展开', + name: 'defaultVisible', + propType: 'bool', + }, + { + description: '弹层在显示和隐藏触发的事件', + name: 'onVisibleChange', + propType: 'func', + }, + { + description: '弹层自定义样式', + name: 'popupStyle', + propType: 'object', + }, + { + description: '弹层自定义样式类', + name: 'popupClassName', + propType: 'string', + }, + { + description: '弹层属性透传', + name: 'popupProps', + propType: 'object', + }, + { + description: '是否跟随滚动', + name: 'followTrigger', + propType: 'bool', + }, + { + defaultValue: [], + description: '默认激活的菜单项(用法同 Menu 非受控)', + name: 'defaultSelectedKeys', + propType: 'array', + }, + { + description: '激活的菜单项(用法同 Menu 受控)', + name: 'selectedKeys', + propType: 'array', + }, + { + description: '菜单的选择模式,同 Menu', + name: 'selectMode', + propType: { + type: 'oneOf', + value: [ + 'single', + 'multiple', + ], + }, + }, + { + description: '点击菜单项后的回调,同 Menu', + name: 'onItemClick', + propType: 'func', + }, + { + description: '选择菜单后的回调,同 Menu', + name: 'onSelect', + propType: 'func', + }, + { + defaultValue: undefined, + description: '菜单属性透传', + name: 'menuProps', + propType: 'object', + }, + { + name: 'style', + propType: 'object', + }, + { + name: 'className', + propType: 'string', + }, + { + name: 'children', + propType: 'any', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Message', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Message', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + defaultValue: false, + name: 'pure', + propType: 'bool', + }, + { + name: 'className', + propType: 'string', + }, + { + name: 'style', + propType: 'object', + }, + { + defaultValue: 'success', + description: '反馈类型', + name: 'type', + propType: { + type: 'oneOf', + value: [ + 'success', + 'warning', + 'error', + 'notice', + 'help', + 'loading', + ], + }, + }, + { + defaultValue: 'inline', + description: '反馈外观', + name: 'shape', + propType: { + type: 'oneOf', + value: [ + 'inline', + 'addon', + 'toast', + ], + }, + }, + { + defaultValue: 'medium', + description: '反馈大小', + name: 'size', + propType: { + type: 'oneOf', + value: [ + 'medium', + 'large', + ], + }, + }, + { + description: '标题', + name: 'title', + propType: 'node', + }, + { + description: '内容', + name: 'children', + propType: 'node', + }, + { + defaultValue: true, + description: '默认是否显示', + name: 'defaultVisible', + propType: 'bool', + }, + { + description: '当前是否显示', + name: 'visible', + propType: 'bool', + }, + { + description: '显示的图标类型,会覆盖内部设置的IconType', + name: 'iconType', + propType: 'string', + }, + { + defaultValue: false, + description: '显示关闭按钮', + name: 'closeable', + propType: 'bool', + }, + { + defaultValue: Function {}, + description: '关闭按钮的回调', + name: 'onClose', + propType: 'func', + }, + { + defaultValue: Function {}, + description: '关闭之后调用的函数', + name: 'afterClose', + propType: 'func', + }, + { + defaultValue: true, + description: '是否开启展开收起动画', + name: 'animation', + propType: 'bool', + }, + { + name: 'locale', + propType: 'object', + }, + { + name: 'rtl', + propType: 'bool', + }, + ], + screenshot: '', + title: '@alifd/next', + }, + { + componentName: 'Nav', + docUrl: '', + npm: { + destructuring: false, + exportName: 'Nav', + main: 'src/index.js', + package: '@alifd/next', + subName: '', + version: '1.19.18', + }, + props: [ + { + defaultValue: 'next-', + name: 'prefix', + propType: 'string', + }, + { + defaultValue: false, + name: 'pure', + propType: 'bool', + }, + { + name: 'rtl', + propType: 'bool', + }, + { + name: 'className', + propType: 'string', + }, + { + name: 'style', + propType: 'object', + }, + { + description: '导航项和子导航', + name: 'children', + propType: 'node', + }, + { + defaultValue: 'normal', + description: `导航类型␊ + @enumdesc 普通, 主要, 次要, 线形`, + name: 'type', + propType: { + type: 'oneOf', + value: [ + 'normal', + 'primary', + 'secondary', + 'line', + ], + }, + }, + { + defaultValue: 'ver', + description: `导航布局␊ + @enumdesc 水平, 垂直`, + name: 'direction', + propType: { + type: 'oneOf', + value: [ + 'hoz', + 'ver', + ], + }, + }, + { + defaultValue: 'left', + description: '横向导航条 items 和 footer 的对齐方向,在 direction 设置为 \'hoz\' 并且 header 存在时生效', + name: 'hozAlign', + propType: { + type: 'oneOf', + value: [ + 'left', + 'right', + ], + }, + }, + { + description: `设置组件选中状态的 active 边方向␊ + @enumdesc 无, 上, 下, 左, 右␊ + @default 当 direction 为 'hoz' 时,默认值为 'bottom',当 direction 为 'ver' 时,默认值为 'left'`, + name: 'activeDirection', + propType: { + type: 'oneOf', + value: [ + null, + 'top', + 'bottom', + 'left', + 'right', + ], + }, + }, + { + defaultValue: 'inline', + description: `子导航打开的模式(水平导航只支持弹出)␊ + @eumdesc 行内, 弹出`, + name: 'mode', + propType: { + type: 'oneOf', + value: [ + 'inline', + 'popup', + ], + }, + }, + { + defaultValue: 'click', + description: '子导航打开的触发方式', + name: 'triggerType', + propType: { + type: 'oneOf', + value: [ + 'click', + 'hover', + ], + }, + }, + { + defaultValue: 20, + description: '内联子导航缩进距离', + name: 'inlineIndent', + propType: 'number', + }, + { + defaultValue: false, + description: '初始展开所有的子导航,只在 mode 设置为 \'inline\' 以及 openMode 设置为 \'multiple\' 下生效', + name: 'defaultOpenAll', + propType: 'bool', + }, + { + defaultValue: 'multiple', + description: `内联子导航的展开模式,同时可以展开一个同级子导航还是多个同级子导航,该属性仅在 mode 为 inline 时生效␊ + @eumdesc 一个, 多个`, + name: 'openMode', + propType: { + type: 'oneOf', + value: [ + 'single', + 'multiple', + ], + }, + }, + { + description: '当前选中导航项的 key 值', + name: 'selectedKeys', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'array', + ], + }, + }, + { + defaultValue: [], + description: '初始选中导航项的 key 值', + name: 'defaultSelectedKeys', + propType: { + type: 'oneOfType', + value: [ + 'string', + 'array', + ], + }, + }, + { + description: `选中或取消选中导航项触发的回调函数␊ + @param {Array} selectedKeys 选中的所有导航项的 key␊ + @param {Object} item 选中或取消选中的导航项␊ + @param {Object} extra 额外参数␊ + @param {Boolean} extra.select 是否是选中␊ + @param {Array} extra.key 导航项的 key␊ + @param {Object} extra.label 导航项的文本␊ + @param {Array} extra.keyPath 导航项 key 的路径`, + name: 'onSelect', + propType: 'func', + }, + { + defaultValue: 'follow', + description: `弹出子导航的对齐方式(水平导航只支持 follow )␊ + @eumdesc Item 顶端对齐, Nav 顶端对齐`, + name: 'popupAlign', + propType: { + type: 'oneOf', + value: [ + 'follow', + 'outside', + ], + }, + }, + { + description: '弹出子导航的自定义类名', + name: 'popupClassName', + propType: 'string', + }, + { + description: '是否只显示图标', + name: 'iconOnly', + propType: 'bool', + }, + { + defaultValue: true, + description: '是否显示右侧的箭头(仅在 iconOnly=true 时生效)', + name: 'hasArrow', + propType: 'bool', + }, + { + defaultValue: false, + description: '是否有 ToolTips (仅在 iconOnly=true 时生效)', + name: 'hasTooltip', + propType: 'bool', + }, + { + description: '自定义导航头部', + name: 'header', + propType: 'node', + }, + { + description: '自定义导航尾部', + name: 'footer', + propType: 'node', + }, + { + defaultValue: false, + description: '是否开启嵌入式模式,一般用于Layout的布局中,开启后没有默认背景、外层border、box-shadow,可以配合`