From 9c62a490c9f8fee7564dec310031755e7478691e Mon Sep 17 00:00:00 2001 From: "jianfang.rjf" Date: Thu, 7 May 2020 21:25:32 +0800 Subject: [PATCH 01/19] feat: add expression-setter AutoComplete tips --- .../setters/src/expression-setter/index.scss | 141 ++------------ .../setters/src/expression-setter/index.tsx | 179 +++++++----------- 2 files changed, 85 insertions(+), 235 deletions(-) diff --git a/packages/setters/src/expression-setter/index.scss b/packages/setters/src/expression-setter/index.scss index 19287099a..0d12a5571 100644 --- a/packages/setters/src/expression-setter/index.scss +++ b/packages/setters/src/expression-setter/index.scss @@ -1,130 +1,17 @@ -// mixin -.lowcode-setter-mixin > * { - vertical-align: middle; -} -.lowcode-setter-mixin { - width: 86%; -} -.lowcode-setter-mixin .next-input { - width: 100%; -} -.lowcode-setter-mixin .next-select-trigger { - width: 100%; -} -// json-setter -// :global { - .nrs-monaco-form { - .next-form-item:last-child { - margin: 0 !important; - } +.expression-setter-item-inner { + .next-menu-item-text { + vertical-align: middle; + display: flex; + justify-content: space-between; } - .monaco-editor-wrap { - .luna-monaco-button .next-icon-first { - height: 26px; - } - .monaco_fullscreen_icon { - position: absolute; - line-height: 1; - z-index: 7; - color: #ddd; - &:hover { - color: #fff; - } - } - .btns-eare { - text-align: left; - line-height: initial; - margin-top: 5px; - // button{ - // margin-right: 10px; - // } - } - &.monaco-nofullscreen { - position: relative !important; - .monaco_fullscreen_icon { - position: absolute; - top: 5px; - right: 5px; - line-height: 1; - z-index: 7; - i:before { - font-size: 16px; - } - } - } - &.monaco-fullscreen { - position: fixed !important; - height: 100% !important; - width: 100% !important; - border: 0; - margin: 0; - top: 0; - bottom: 0; - left: 0; - right: 0; - z-index: 1001; - overflow: hidden; - .monaco_fullscreen_icon { - top: 10px; - right: 10px; - i:before { - font-size: 24px; - } - } - } - } - .luna-monaco-button button { - width: 100%; - } - .luna-monaco-button-dialog { - .next-dialog-body { - padding: 0; - .next-form-item { - height: 100%; - margin-bottom: 0; - .next-form-item-control, - .next-form-item-control > div { - height: 100% !important; - } - .next-form-item-help { - position: absolute; - } - } - } - } -// } -// color-setter -.lowcode-color-box { - margin-right: -5px; - padding: 3px 0 3px 3px; - width: 26px; - height: 26px; - display: inline-block; - div { - width: 20px; - height: 20px; - border: 1px solid #ddd; + .next-menu-item { + padding: 0 10px; } } -.next-balloon-normal.lowcode-color-content { - padding: 0; - background: #ffffff; - border-radius: 0; - border: 1px solid #e5e5e5; - box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1); - &:after { - display: none; - } - .sketch-picker { - border-radius: 0 !important; - border: none !important; - box-shadow: none !important; - .flexbox-fix { - input { - width: 100% !important; - min-width: 30px; - text-align: center; - } - } - } -} \ No newline at end of file +.code-input-value { + float: left; +} +.code-input-help { + float: right; + color: #6897f2; +} diff --git a/packages/setters/src/expression-setter/index.tsx b/packages/setters/src/expression-setter/index.tsx index 7e4ecd14b..2ab0e7a3c 100644 --- a/packages/setters/src/expression-setter/index.tsx +++ b/packages/setters/src/expression-setter/index.tsx @@ -1,24 +1,26 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import { Select, Balloon } from '@alife/next'; +import { Select, Balloon, Icon } from '@alife/next'; import * as acorn from 'acorn'; import { isJSExpression, generateI18n } from './locale/utils'; import zhCN from './locale/zh-CN'; +import './index.scss'; + const { Option, AutoComplete } = Select; const { Tooltip } = Balloon; const helpMap = { this: '容器上下文对象', - 'this.state': '容器的state', - 'this.props': '容器的props', - 'this.context': '容器的context', - 'this.page': '页面上下文对象', - 'this.component': '组件上下文对象', - 'this.constants': '应用常量对象', - 'this.utils': '应用工具对象', - 'this.dataSourceMap': '容器数据源Map', - 'this.field': '表单Field对象' + 'state': '容器的state', + 'props': '容器的props', + 'context': '容器的context', + 'schema': '页面上下文对象', + 'component': '组件上下文对象', + 'constants': '应用常量对象', + 'utils': '应用工具对象', + 'dataSourceMap': '容器数据源Map', + 'field': '表单Field对象' } export default class ExpressionView extends PureComponent { @@ -45,7 +47,7 @@ export default class ExpressionView extends PureComponent { i18n: any; t: void; $input: any; - listenerFun: (event: any) => void; + listenerFun: ((event: any) => void) | undefined; static getInitValue(val: { value: any; match: (arg0: RegExp) => any; }) { if (isJSExpression(val)) { @@ -81,51 +83,12 @@ export default class ExpressionView extends PureComponent { onChange(value: string, actionType: string) { let realInputValue = value; let realDataSource = null; - const cursorIndex = this.getInputCursorPosition(); let nextCursorIndex: number; //更新值 if (actionType === 'itemClick' || actionType === 'enter') { let curValue = this.state.value; if (curValue) { - //如果是非.结束,则替换当前这个变量; - let preStr = curValue.substr(0, cursorIndex); - let nextStr = curValue.substr(cursorIndex); - let preArr = preStr.split('.'); - let preArrLen = preArr.length; - let tarPreStr = ''; - if (!preArr[preArrLen - 1]) { - //如果是.结束,则增加到.后面 - if (preArr[preArrLen - 2] === 'this') { - preArr = preArr.slice(0, preArrLen - 2); - preArr.push(value); - tarPreStr = preArr.join('.'); - } else { - tarPreStr = preStr + value; - } - } else { - if (preArr[preArrLen - 2] === 'this') { - preArr = preArr.slice(0, preArrLen - 2); - } else { - preArr = preArr.slice(0, preArrLen - 1); - } - preArr.push(value); - tarPreStr = preArr.join('.'); - } - realInputValue = tarPreStr + nextStr; - realDataSource = this.getDataSource(tarPreStr + '.') || []; - nextCursorIndex = tarPreStr.length; - } - } else { - let tarPreStr = value.substr(0, cursorIndex); - if (tarPreStr) { - let lastChar = tarPreStr.charAt(tarPreStr.length - 1); - if (lastChar === '.') { - realDataSource = this.getDataSource(tarPreStr) || []; - } else { - realDataSource = this.getDataSource(tarPreStr + '.'); - } - } else { - realDataSource = this.getDataSource('this.'); + realInputValue = curValue + realInputValue; } } //更新数据源 @@ -154,21 +117,16 @@ export default class ExpressionView extends PureComponent { * @return {Array} */ getDataSource(tempStr: string): Array { - if (tempStr === '' || /[^\w\.]$/.test(tempStr)) { - return this.getDataSource('this.') || []; + if (/[^\w\.]$/.test(tempStr)) { + return []; + } else if (tempStr === null || tempStr === '') { + return this.getContextKeys([]); } else if (/\w\.$/.test(tempStr)) { let currentField = this.getCurrentFiled(tempStr); if (!currentField) return null; let tempKeys = this.getObjectKeys(currentField.str); tempKeys = this.getContextKeys(tempKeys); if (!tempKeys) return null; - //给默认情况增加this - if (tempStr === 'this.') { - tempKeys = tempKeys.map((item: string) => { - return 'this.' + item; - }); - tempKeys.unshift('this'); - } return tempKeys; } else if (/\.$/.test(tempStr)) { return []; @@ -202,34 +160,31 @@ export default class ExpressionView extends PureComponent { * @param {Array} * @return {Array} */ - getContextKeys(keys: any) { - // let context = {}; - // const { appHelper } = this.context; - // const activeKey = appHelper && appHelper.activeKey; - // if (!activeKey) return; - // const activeCtx = appHelper.schemaHelper.compCtxMap && appHelper.schemaHelper.compCtxMap[activeKey]; - // if (!activeCtx) return null; - // let __self = activeCtx; - // if (keys && keys.length > 1) { - // keys.shift(0); - // let path = '/' + keys.join('/'); - // path = path.replace(/[\[\]]/g, '/'); - // context = jsonuri.get(__self, path); - // if (context && typeof context === 'object') { - // return this.filterKey(context); - // } - // } else if (keys && keys[0] === 'this') { - // return this.filterKey(__self); - // } - // return null; - return [ - "page", - "component" - ] + getContextKeys(keys: []) { + const editor = this.props.field.editor; + console.log(editor); + const limitKeys = ['schema', 'utils', 'constants']; + if (keys.length === 0) return limitKeys; + if (!limitKeys.includes(keys[0])) return []; + let result = []; + let keyValue = editor; + let assert = false; + keys.forEach(item => { + if (!keyValue[item] || typeof keyValue[item] !== 'object') { + assert = true; + } + if (keyValue[item]) { + keyValue = keyValue[item]; + } + }) + if (assert) return []; + result = Object.keys(keyValue); + return result; + // return utilsKeys.concat(constantsKeys).concat(schemaKeys); } /*过滤key */ - filterKey(obj: any) { + filterKey(obj: any, name: string) { let filterKeys = [ 'reloadDataSource', 'REACT_HOT_LOADER_RENDERED_GENERATION', @@ -244,7 +199,7 @@ export default class ExpressionView extends PureComponent { let result = []; for (let key in obj) { if (key.indexOf('_') !== 0 && filterKeys.indexOf(key) === -1) { - result.push(key); + result.push(`${name}.${key}`); } } return result; @@ -259,12 +214,16 @@ export default class ExpressionView extends PureComponent { filterOption(inputValue: string, item: { value: string | any[]; }) { const cursorIndex = this.getInputCursorPosition(); let preStr = inputValue.substr(0, cursorIndex); - let lastKey = preStr.split('.').slice(-1); + let lastKey: string[] = preStr.split('.').slice(-1); if (!lastKey) return true; if (item.value.indexOf(lastKey) > -1) return true; return false; } + // handleClick = () => { + // this.props.field.editor.emit('variableBindDialog.open'); + // } + render() { const { value, dataSource } = this.state; const { placeholder } = this.props; @@ -293,26 +252,30 @@ export default class ExpressionView extends PureComponent { isValObject ? ( value ) : ( - {'{{'}} - innerAfter={{'}}'}} - itemRender={({ value }) => { - return ( - - ); - }} - onChange={this.onChange.bind(this)} - filter={this.filterOption.bind(this)} - /> +
+ {'{{'}} + innerAfter={{'}}'}} + popupClassName="expression-setter-item-inner" + itemRender={({ value }) => { + console.log(value); + return ( + + ); + }} + onChange={this.onChange.bind(this)} + filter={this.filterOption.bind(this)} + /> +
) } > @@ -363,7 +326,7 @@ export default class ExpressionView extends PureComponent { * 字符串取得对象keys */ getObjectKeys(str: string) { - let keys = []; + let keys: string | any[] = []; if (str) keys = str.split('.'); return keys.slice(0, keys.length - 1); } From 0e50a2056a9c00bfddfe25af82462a781e231910 Mon Sep 17 00:00:00 2001 From: "wuyue.xht" Date: Mon, 18 May 2020 16:03:00 +0800 Subject: [PATCH 02/19] feat: ReactProvider --- packages/react-provider/CHANGELOG.md | 4 + packages/react-provider/README.md | 1 + packages/react-provider/build.json | 5 + packages/react-provider/package.json | 43 ++++++++ packages/react-provider/src/index.ts | 5 + .../react-provider/src/lazy-component.tsx | 43 ++++++++ packages/react-provider/src/provider.tsx | 104 ++++++++++++++++++ packages/react-provider/tsconfig.json | 9 ++ 8 files changed, 214 insertions(+) create mode 100644 packages/react-provider/CHANGELOG.md create mode 100644 packages/react-provider/README.md create mode 100644 packages/react-provider/build.json create mode 100644 packages/react-provider/package.json create mode 100644 packages/react-provider/src/index.ts create mode 100644 packages/react-provider/src/lazy-component.tsx create mode 100644 packages/react-provider/src/provider.tsx create mode 100644 packages/react-provider/tsconfig.json diff --git a/packages/react-provider/CHANGELOG.md b/packages/react-provider/CHANGELOG.md new file mode 100644 index 000000000..e4d87c4d4 --- /dev/null +++ b/packages/react-provider/CHANGELOG.md @@ -0,0 +1,4 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. diff --git a/packages/react-provider/README.md b/packages/react-provider/README.md new file mode 100644 index 000000000..f37b9b0f6 --- /dev/null +++ b/packages/react-provider/README.md @@ -0,0 +1 @@ +# 低代码引擎运行时框架 diff --git a/packages/react-provider/build.json b/packages/react-provider/build.json new file mode 100644 index 000000000..bd5cf18dd --- /dev/null +++ b/packages/react-provider/build.json @@ -0,0 +1,5 @@ +{ + "plugins": [ + "build-plugin-component" + ] +} diff --git a/packages/react-provider/package.json b/packages/react-provider/package.json new file mode 100644 index 000000000..29cca8ab3 --- /dev/null +++ b/packages/react-provider/package.json @@ -0,0 +1,43 @@ +{ + "name": "@ali/lowcode-react-provider", + "version": "0.8.14", + "description": "React Provider for Runtime", + "files": [ + "es", + "lib" + ], + "main": "lib/index.js", + "module": "es/index.js", + "scripts": { + "build": "build-scripts build --skip-demo", + "test": "ava", + "test:snapshot": "ava --update-snapshots" + }, + "ava": { + "compileEnhancements": false, + "snapshotDir": "test/fixtures/__snapshots__", + "extensions": [ + "ts" + ], + "require": [ + "ts-node/register" + ] + }, + "license": "MIT", + "dependencies": { + "@ali/lowcode-runtime": "^0.8.13", + "react": "^16", + "react-dom": "^16", + "@recore/router": "^1.0.11" + }, + "devDependencies": { + "@alib/build-scripts": "^0.1.18", + "build-plugin-component": "^0.2.16", + "@types/node": "^13.7.1", + "@types/react": "^16", + "@types/react-dom": "^16" + }, + "publishConfig": { + "registry": "https://registry.npm.alibaba-inc.com" + } +} diff --git a/packages/react-provider/src/index.ts b/packages/react-provider/src/index.ts new file mode 100644 index 000000000..515d26e3e --- /dev/null +++ b/packages/react-provider/src/index.ts @@ -0,0 +1,5 @@ +import ReactProvider from './provider'; +import { Router } from '@recore/router'; + +export { Router }; +export default ReactProvider; diff --git a/packages/react-provider/src/lazy-component.tsx b/packages/react-provider/src/lazy-component.tsx new file mode 100644 index 000000000..b20fd2bf8 --- /dev/null +++ b/packages/react-provider/src/lazy-component.tsx @@ -0,0 +1,43 @@ +import { Component, createElement } from 'react'; +import { app } from '@ali/lowcode-runtime'; + +interface IProps { + getPageData: () => any; + [key: string]: any; +} + +interface IState { + schema: object | null; +} + +export default class LazyComponent extends Component { + constructor(props: IProps) { + super(props); + this.state = { + schema: null, + }; + } + + async componentDidMount() { + const { getPageData } = this.props; + if (getPageData && !this.state.schema) { + const schema = await getPageData(); + this.setState({ schema }); + } + } + + render() { + const { getPageData, ...restProps } = this.props; + const { schema } = this.state; + const Renderer = app.getRenderer(); + const Loading = app.getLoading(); + if (!Renderer || !schema) { + if (!Loading) { + return null; + } + // loading扩展点 + return createElement(Loading); + } + return createElement(Renderer as any, { schema, loading: Loading ? createElement(Loading) : null, ...restProps }); + } +} diff --git a/packages/react-provider/src/provider.tsx b/packages/react-provider/src/provider.tsx new file mode 100644 index 000000000..de4d97352 --- /dev/null +++ b/packages/react-provider/src/provider.tsx @@ -0,0 +1,104 @@ +import { createElement, ReactType, ReactElement } from 'react'; +import ReactDOM from 'react-dom'; +import { Router } from '@recore/router'; +import { app, Provider } from '@ali/lowcode-runtime'; +import LazyComponent from './lazy-component'; + +export default class ReactProvider extends Provider { + // 定制构造根组件的逻辑,如切换路由机制 + createApp() { + const RouterView = this.getRouterView(); + let App; + const layoutConfig = this.getLayoutConfig(); + if (!layoutConfig || !layoutConfig.componentName) { + App = (props: any) => (RouterView ? createElement(RouterView, { ...props }) : null); + return App; + } + const { componentName: layoutName, props: layoutProps } = layoutConfig; + const { content: Layout, props: extraLayoutProps } = app.getLayout(layoutName) || {}; + const sectionalRender = this.isSectionalRender(); + if (!sectionalRender && Layout) { + App = (props: any) => + createElement( + Layout, + { ...layoutProps, ...extraLayoutProps }, + RouterView ? createElement(RouterView, props) : null, + ); + } else { + App = (props: any) => (RouterView ? createElement(RouterView, props) : null); + } + return App; + } + + runApp(App: any, config: any) { + ReactDOM.render(, document.getElementById(config?.containerId || 'App')); + } + + // 内置实现 for 动态化渲染 + getRouterView(): ReactType | null { + const routerConfig = this.getRouterConfig(); + if (!routerConfig) { + return null; + } + const routes: Array<{ + path: string; + children: any; + exact: boolean; + defined: { keepAlive: boolean; [key: string]: any }; + }> = []; + let homePageId = this.getHomePage(); + Object.keys(routerConfig).forEach((pageId: string, idx: number) => { + if (!pageId) { + return; + } + const path = routerConfig[pageId]; + routes.push({ + path, + children: (props: any) => this.getLazyComponent(pageId, props), + exact: true, + defined: { keepAlive: true }, + }); + if (homePageId) { + return; + } + if (idx === 0 || path === '/') { + homePageId = pageId; + } + }); + if (homePageId) { + routes.push({ + path: '**', + children: (props: any) => this.getLazyComponent(homePageId, { ...props }), + exact: true, + defined: { keepAlive: true }, + }); + } + const RouterView = (props: any) => { + return createElement(Router as any, { + routes, + components: this.getComponents(), + utils: this.getUtils(), + componentsMap: this.getComponentsMapObj(), + ...props, + }); + }; + return RouterView; + } + + getLazyComponent(pageId: string, props: any): ReactElement | null { + if (!pageId) { + return null; + } + if (this.getlazyElement(pageId)) { + return this.getlazyElement(pageId); + } else { + const lazyElement = createElement(LazyComponent as any, { + getPageData: async () => await this.getPageData(pageId), + key: pageId, + ...props, + }); + this.setlazyElement(pageId, lazyElement); + return lazyElement; + } + } +} diff --git a/packages/react-provider/tsconfig.json b/packages/react-provider/tsconfig.json new file mode 100644 index 000000000..c37b76ecc --- /dev/null +++ b/packages/react-provider/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": [ + "./src/" + ] +} From 8e361967e04eb353dda286dc7d90bbd7538dcb3e Mon Sep 17 00:00:00 2001 From: "wuyue.xht" Date: Mon, 18 May 2020 16:03:44 +0800 Subject: [PATCH 03/19] =?UTF-8?q?chore:=20=E6=8A=BD=E7=A6=BBReact?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/runtime/build.json | 6 +- packages/runtime/package.json | 10 +- packages/runtime/src/core/container.ts | 17 ++- packages/runtime/src/core/index.ts | 15 ++- .../core/{provider/index.ts => provider.ts} | 7 +- .../runtime/src/core/provider/react/index.ts | 100 ------------------ .../core/provider/react/lazy-component.tsx | 43 -------- .../runtime/src/core/{run.ts => runApp.ts} | 54 +++++----- packages/runtime/src/index.ts | 4 +- 9 files changed, 53 insertions(+), 203 deletions(-) rename packages/runtime/src/core/{provider/index.ts => provider.ts} (96%) delete mode 100644 packages/runtime/src/core/provider/react/index.ts delete mode 100644 packages/runtime/src/core/provider/react/lazy-component.tsx rename packages/runtime/src/core/{run.ts => runApp.ts} (50%) diff --git a/packages/runtime/build.json b/packages/runtime/build.json index e791d5b6b..bd5cf18dd 100644 --- a/packages/runtime/build.json +++ b/packages/runtime/build.json @@ -1,9 +1,5 @@ { "plugins": [ - "build-plugin-component", - "build-plugin-fusion", - ["build-plugin-moment-locales", { - "locales": ["zh-cn"] - }] + "build-plugin-component" ] } diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 2c667549f..276e1cc32 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@ali/lowcode-runtime", - "version": "0.8.12", + "version": "0.8.14", "description": "Runtime for Ali lowCode engine", "files": [ "es", @@ -25,15 +25,13 @@ }, "license": "MIT", "dependencies": { - "@ali/recore": "^1.6.9", - "@ali/offline-events": "^1.0.0" + "@ali/offline-events": "^1.0.0", + "history": "^4.10.1" }, "devDependencies": { "@alib/build-scripts": "^0.1.18", "@types/node": "^13.7.1", - "@types/react": "^16", - "@types/react-dom": "^16", - "build-plugin-component": "^0.2.11" + "build-plugin-component": "^0.2.16" }, "publishConfig": { "registry": "https://registry.npm.alibaba-inc.com" diff --git a/packages/runtime/src/core/container.ts b/packages/runtime/src/core/container.ts index e8deee816..809394bf6 100644 --- a/packages/runtime/src/core/container.ts +++ b/packages/runtime/src/core/container.ts @@ -1,4 +1,3 @@ -import { ReactType } from 'react'; import Provider from './provider'; export interface ILayoutOptions { @@ -7,16 +6,16 @@ export interface ILayoutOptions { } export default class Container { - private renderer: ReactType | null = null; - private layouts: { [key: string]: { content: ReactType; props: any } } = {}; - private loading: ReactType | null = null; + private renderer: any = null; + private layouts: { [key: string]: { content: any; props: any } } = {}; + private loading: any = null; private provider: any; - registerRenderer(renderer: ReactType): any { + registerRenderer(renderer: any): any { this.renderer = renderer; } - registerLayout(Layout: ReactType, options: ILayoutOptions): any { + registerLayout(Layout: any, options: ILayoutOptions): any { if (!options) { return; } @@ -27,7 +26,7 @@ export default class Container { this.layouts[componentName] = { content: Layout, props }; } - registerLoading(component: ReactType) { + registerLoading(component: any) { if (!component) { return; } @@ -50,11 +49,11 @@ export default class Container { return this.layouts[componentName]; } - getRenderer(): ReactType | null { + getRenderer(): any { return this.renderer; } - getLoading(): ReactType | null { + getLoading(): any { return this.loading; } diff --git a/packages/runtime/src/core/index.ts b/packages/runtime/src/core/index.ts index 6160a6a20..de70f9229 100644 --- a/packages/runtime/src/core/index.ts +++ b/packages/runtime/src/core/index.ts @@ -1,7 +1,6 @@ -import { ReactType } from 'react'; import Container, { ILayoutOptions } from './container'; import { IProvider } from './provider'; -import run from './run'; +import runApp from './runApp'; class App { private container: Container; @@ -11,18 +10,18 @@ class App { } run() { - run(); + runApp(); } - registerRenderer(renderer: ReactType): any { + registerRenderer(renderer: any): any { this.container.registerRenderer(renderer); } - registerLayout(Layout: ReactType, options: ILayoutOptions): any { + registerLayout(Layout: any, options: ILayoutOptions): any { this.container.registerLayout(Layout, options); } - registerLoading(component: ReactType) { + registerLoading(component: any) { this.container.registerLoading(component); } @@ -34,11 +33,11 @@ class App { return this.container.getLayout(componentName); } - getRenderer(): ReactType | null { + getRenderer(): any | null { return this.container.getRenderer(); } - getLoading(): ReactType | null { + getLoading(): any | null { return this.container.getLoading(); } diff --git a/packages/runtime/src/core/provider/index.ts b/packages/runtime/src/core/provider.ts similarity index 96% rename from packages/runtime/src/core/provider/index.ts rename to packages/runtime/src/core/provider.ts index 79016f2ab..907dd4903 100644 --- a/packages/runtime/src/core/provider/index.ts +++ b/packages/runtime/src/core/provider.ts @@ -1,4 +1,4 @@ -import { IAppConfig, IUtils, IComponents, HistoryMode } from '../run'; +import { IAppConfig, IUtils, IComponents, HistoryMode } from './runApp'; import EventEmitter from '@ali/offline-events'; interface IConstants { @@ -110,6 +110,7 @@ export interface IProvider { getPageData(pageId: string): Promise; getLazyComponent(pageId: string, props: any): any; createApp(): void; + runApp(App: any, config: IAppConfig): void; } export default class Provider implements IProvider { @@ -197,6 +198,10 @@ export default class Provider implements IProvider { throw new Error('Method called "createApp" not implemented.'); } + runApp(App: any, config: IAppConfig) { + throw new Error('Method called "runApp" not implemented.'); + } + registerComponents(components: IComponents | undefined) { if (!components) { return; diff --git a/packages/runtime/src/core/provider/react/index.ts b/packages/runtime/src/core/provider/react/index.ts deleted file mode 100644 index 1c0a3fb07..000000000 --- a/packages/runtime/src/core/provider/react/index.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { createElement, ReactType, ReactElement } from 'react'; -import { Router } from '@ali/recore'; -import app from '../../index'; -import Provider from '..'; -import LazyComponent from './lazy-component'; - -export default class ReactProvider extends Provider { - // 定制构造根组件的逻辑,如切换路由机制 - createApp() { - const RouterView = this.getRouterView(); - let App; - const layoutConfig = this.getLayoutConfig(); - if (!layoutConfig || !layoutConfig.componentName) { - App = (props: any) => (RouterView ? createElement(RouterView, { ...props }) : null); - return App; - } - const { componentName: layoutName, props: layoutProps } = layoutConfig; - const { content: Layout, props: extraLayoutProps } = app.getLayout(layoutName) || {}; - const sectionalRender = this.isSectionalRender(); - if (!sectionalRender && Layout) { - App = (props: any) => - createElement( - Layout, - { ...layoutProps, ...extraLayoutProps }, - RouterView ? createElement(RouterView, props) : null, - ); - } else { - App = (props: any) => (RouterView ? createElement(RouterView, props) : null); - } - return App; - } - - // 内置实现 for 动态化渲染 - getRouterView(): ReactType | null { - const routerConfig = this.getRouterConfig(); - if (!routerConfig) { - return null; - } - const routes: Array<{ - path: string; - children: any; - exact: boolean; - defined: { keepAlive: boolean; [key: string]: any }; - }> = []; - let homePageId = this.getHomePage(); - Object.keys(routerConfig).forEach((pageId: string, idx: number) => { - if (!pageId) { - return; - } - const path = routerConfig[pageId]; - routes.push({ - path, - children: (props: any) => this.getLazyComponent(pageId, props), - exact: true, - defined: { keepAlive: true }, - }); - if (homePageId) { - return; - } - if (idx === 0 || path === '/') { - homePageId = pageId; - } - }); - if (homePageId) { - routes.push({ - path: '**', - children: (props: any) => this.getLazyComponent(homePageId, { ...props }), - exact: true, - defined: { keepAlive: true }, - }); - } - const RouterView = (props: any) => { - return createElement(Router as any, { - routes, - components: this.getComponents(), - utils: this.getUtils(), - componentsMap: this.getComponentsMapObj(), - ...props, - }); - }; - return RouterView; - } - - getLazyComponent(pageId: string, props: any): ReactElement | null { - if (!pageId) { - return null; - } - if (this.getlazyElement(pageId)) { - return this.getlazyElement(pageId); - } else { - const lazyElement = createElement(LazyComponent as any, { - getPageData: async () => await this.getPageData(pageId), - key: pageId, - ...props, - }); - this.setlazyElement(pageId, lazyElement); - return lazyElement; - } - } -} diff --git a/packages/runtime/src/core/provider/react/lazy-component.tsx b/packages/runtime/src/core/provider/react/lazy-component.tsx deleted file mode 100644 index 37c111097..000000000 --- a/packages/runtime/src/core/provider/react/lazy-component.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Component, createElement } from 'react'; -import app from '../../index'; - -interface IProps { - getPageData: () => any; - [key: string]: any; -} - -interface IState { - schema: object | null; -} - -export default class LazyComponent extends Component { - constructor(props: IProps) { - super(props); - this.state = { - schema: null, - }; - } - - async componentDidMount() { - const { getPageData } = this.props; - if (getPageData && !this.state.schema) { - const schema = await getPageData(); - this.setState({ schema }); - } - } - - render() { - const { getPageData, ...restProps } = this.props; - const { schema } = this.state; - const Renderer = app.getRenderer(); - const Loading = app.getLoading(); - if (!Renderer || !schema) { - if (!Loading) { - return null; - } - // loading扩展点 - return createElement(Loading); - } - return createElement(Renderer as any, { schema, loading: Loading ? createElement(Loading) : null, ...restProps }); - } -} diff --git a/packages/runtime/src/core/run.ts b/packages/runtime/src/core/runApp.ts similarity index 50% rename from packages/runtime/src/core/run.ts rename to packages/runtime/src/core/runApp.ts index 7cc7ab98f..6fb287f99 100644 --- a/packages/runtime/src/core/run.ts +++ b/packages/runtime/src/core/runApp.ts @@ -1,6 +1,4 @@ -import { ReactType } from 'react'; -import { runApp } from '@ali/recore'; -import { HashHistoryBuildOptions, BrowserHistoryBuildOptions, MemoryHistoryBuildOptions } from '@recore/history'; +import { HashHistoryBuildOptions, BrowserHistoryBuildOptions, MemoryHistoryBuildOptions } from 'history'; import app from './index'; export type HistoryOptions = { @@ -8,7 +6,7 @@ export type HistoryOptions = { } & (HashHistoryBuildOptions | BrowserHistoryBuildOptions | MemoryHistoryBuildOptions); export interface IComponents { - [key: string]: ReactType; + [key: string]: any; } export interface IUtils { @@ -22,34 +20,35 @@ export interface IAppConfig { components?: IComponents; utils?: IUtils; containerId?: string; + [key: string]: any; } -export interface IRecoreAppConfig { - history?: HistoryMode; - globalComponents?: IComponents; - globalUtils?: IUtils; - containerId?: string; -} +// export interface IRecoreAppConfig { +// history?: HistoryMode; +// globalComponents?: IComponents; +// globalUtils?: IUtils; +// containerId?: string; +// } -function transformConfig(config: IAppConfig | (() => IAppConfig)): IRecoreAppConfig { - if (!config) { - return {}; - } - if (typeof config === 'function') { - config = config(); - } - return { - history: config.history, - globalComponents: config.components, - globalUtils: config.utils, - containerId: config.containerId, - }; -} +// function transformConfig(config: IAppConfig | (() => IAppConfig)): IRecoreAppConfig { +// if (!config) { +// return {}; +// } +// if (typeof config === 'function') { +// config = config(); +// } +// return { +// history: config.history, +// globalComponents: config.components, +// globalUtils: config.utils, +// containerId: config.containerId, +// }; +// } -export default function run() { +export default function runApp() { const provider = app.getProvider(); if (!provider) { - throw new Error(''); + throw new Error('Please register class Provider'); } provider.onReady(() => { const promise = provider.async(); @@ -58,8 +57,7 @@ export default function run() { return; } const App = provider.createApp(); - config = transformConfig(config); - runApp(App, config); + provider.runApp(App, config); }); }); } diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index bb8e079a1..7a0999d95 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -1,7 +1,5 @@ -import { navigator, Router } from '@ali/recore'; import Provider from './core/provider'; -import ReactProvider from './core/provider/react'; import app from './core'; import * as Utils from './utils'; -export { app, Router, Provider, ReactProvider, navigator, Utils }; +export { app, Provider, Utils }; From 7a77c55167865353273e0edeac65fd3e2a09aea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=87=91=E7=A6=85?= Date: Mon, 18 May 2020 21:20:55 +0800 Subject: [PATCH 04/19] use registerSetter from lowcode-editor-core --- packages/demo/public/assets.json | 21 ++++++++++++++----- packages/demo/src/editor/index.tsx | 2 +- .../designer/src/designer/setting/utils.js | 2 +- packages/editor-preset-general/src/index.ts | 5 ++++- packages/editor-setters/src/index.tsx | 2 +- packages/plugin-source-editor/src/index.tsx | 2 +- 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/demo/public/assets.json b/packages/demo/public/assets.json index a2eae1a6a..2008593ea 100644 --- a/packages/demo/public/assets.json +++ b/packages/demo/public/assets.json @@ -40,7 +40,13 @@ } }, { - "componentName": "Div", + "componentName": "Card", + "npm": { + "package": "@alifd/next", + "version": "1.19.18", + "destructuring": true, + "exportName": "Card" + }, "title": "容器", "configure": { "component": { @@ -197,6 +203,10 @@ "exportName": "Input" }, "props": [{ + "name": "value", + "propType": "string", + "description": "" + },{ "name": "label", "propType": "node", "description": "label" @@ -1503,7 +1513,7 @@ "children": [{ "componentName": "Button", "title": "按钮", - "icon": "", + "icon": "add", "package": "@alife/next", "library": "Next", "snippets": [{ @@ -1532,7 +1542,8 @@ "schema": { "componentName": "Button", "props": { - "type": "normal" + "type": "normal", + "value": "normal" }, "children": "normal" } @@ -1588,7 +1599,7 @@ "title": "其他", "icon": "", "children": [{ - "componentName": "Div", + "componentName": "Card", "library": "Next", "title": "容器", "icon": "", @@ -1596,7 +1607,7 @@ "title": "默认", "screenshot": "", "schema": { - "componentName": "Div", + "componentName": "Card", "props": {} } }] diff --git a/packages/demo/src/editor/index.tsx b/packages/demo/src/editor/index.tsx index 041acd148..554cb1e6b 100644 --- a/packages/demo/src/editor/index.tsx +++ b/packages/demo/src/editor/index.tsx @@ -1,5 +1,5 @@ import { render } from 'react-dom'; -import GeneralWorkbench from '@ali/lowcode-editor-preset-general'; +import GeneralWorkbench, { editor } from '../../../editor-preset-general/src'; import config from './config'; import components from './components'; import './global.scss'; diff --git a/packages/designer/src/designer/setting/utils.js b/packages/designer/src/designer/setting/utils.js index ab09545e6..055f2fb8b 100644 --- a/packages/designer/src/designer/setting/utils.js +++ b/packages/designer/src/designer/setting/utils.js @@ -48,7 +48,7 @@ export class Transducer { if (typeof setter === 'string') { setter = getSetter(setter)?.component; if (!setter) { - debugger; + // debugger; } } diff --git a/packages/editor-preset-general/src/index.ts b/packages/editor-preset-general/src/index.ts index c90cdbaeb..79f0dcc48 100644 --- a/packages/editor-preset-general/src/index.ts +++ b/packages/editor-preset-general/src/index.ts @@ -57,7 +57,10 @@ export default function GeneralWorkbench(props: any) { ...props, }); } - +window.__ctx = { + editor, + appHelper: editor, +}; export function init(container?: Element) { if (!container) { container = document.createElement('div'); diff --git a/packages/editor-setters/src/index.tsx b/packages/editor-setters/src/index.tsx index edbc26fd5..474aad89f 100644 --- a/packages/editor-setters/src/index.tsx +++ b/packages/editor-setters/src/index.tsx @@ -1,4 +1,4 @@ -import { registerSetter } from '@ali/lowcode-globals'; +import { registerSetter } from '@ali/lowcode-editor-core'; import { DatePicker, Input, Radio, Select, Switch, NumberPicker } from '@alifd/next'; import ExpressionSetter from './expression-setter'; import ColorSetter from './color-setter'; diff --git a/packages/plugin-source-editor/src/index.tsx b/packages/plugin-source-editor/src/index.tsx index 0a4aaefdb..7db14fce4 100644 --- a/packages/plugin-source-editor/src/index.tsx +++ b/packages/plugin-source-editor/src/index.tsx @@ -58,7 +58,7 @@ export default class SourceEditor extends Component<{ componentWillMount() { const { editor } = this.props; editor.on('leftPanel.show', (key: String) => { - debugger; + // debugger; if (key === 'sourceEditor' && !this.monocoEditor) { this.setState({ isShow: true, From 93cd908adb92946802607ac32d2f47acb5872df3 Mon Sep 17 00:00:00 2001 From: "wuyue.xht" Date: Tue, 19 May 2020 21:03:13 +0800 Subject: [PATCH 05/19] chore(runtime): index.d.ts --- packages/runtime/index.d.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/runtime/index.d.ts b/packages/runtime/index.d.ts index 6e3013290..76ca8e969 100644 --- a/packages/runtime/index.d.ts +++ b/packages/runtime/index.d.ts @@ -1,8 +1,7 @@ -import { ReactType } from 'react'; type HistoryMode = 'browser' | 'hash'; interface ComponentsMap { - [key: string]: ReactType; + [key: string]: any; } interface UtilsMap { From cb0f3827c775be8ef58fb64367c45fb5a707c6a1 Mon Sep 17 00:00:00 2001 From: "wuyue.xht" Date: Tue, 19 May 2020 21:03:34 +0800 Subject: [PATCH 06/19] feat(rax-provider): init --- packages/rax-provider/CHANGELOG.md | 4 + packages/rax-provider/README.md | 1 + packages/rax-provider/build.json | 11 +++ packages/rax-provider/package.json | 30 ++++++ packages/rax-provider/src/index.js | 5 + packages/rax-provider/src/lazy-component.js | 28 ++++++ packages/rax-provider/src/provider.js | 103 ++++++++++++++++++++ packages/rax-provider/src/router.js | 26 +++++ 8 files changed, 208 insertions(+) create mode 100644 packages/rax-provider/CHANGELOG.md create mode 100644 packages/rax-provider/README.md create mode 100644 packages/rax-provider/build.json create mode 100644 packages/rax-provider/package.json create mode 100644 packages/rax-provider/src/index.js create mode 100644 packages/rax-provider/src/lazy-component.js create mode 100644 packages/rax-provider/src/provider.js create mode 100644 packages/rax-provider/src/router.js diff --git a/packages/rax-provider/CHANGELOG.md b/packages/rax-provider/CHANGELOG.md new file mode 100644 index 000000000..e9fb6ecf5 --- /dev/null +++ b/packages/rax-provider/CHANGELOG.md @@ -0,0 +1,4 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. \ No newline at end of file diff --git a/packages/rax-provider/README.md b/packages/rax-provider/README.md new file mode 100644 index 000000000..f37b9b0f6 --- /dev/null +++ b/packages/rax-provider/README.md @@ -0,0 +1 @@ +# 低代码引擎运行时框架 diff --git a/packages/rax-provider/build.json b/packages/rax-provider/build.json new file mode 100644 index 000000000..3edf14380 --- /dev/null +++ b/packages/rax-provider/build.json @@ -0,0 +1,11 @@ +{ + "plugins": [ + [ + "build-plugin-rax-component", + { + "type": "rax", + "targets": ["web"] + } + ] + ] +} diff --git a/packages/rax-provider/package.json b/packages/rax-provider/package.json new file mode 100644 index 000000000..62712f312 --- /dev/null +++ b/packages/rax-provider/package.json @@ -0,0 +1,30 @@ +{ + "name": "@ali/lowcode-rax-provider", + "version": "0.8.14-beta.0", + "description": "Rax Provider for Runtime", + "files": [ + "es", + "lib" + ], + "main": "lib/index.js", + "module": "es/index.js", + "scripts": { + "start": "build-scripts start", + "build": "build-scripts build" + }, + "license": "MIT", + "dependencies": { + "@ali/lowcode-runtime": "^0.8.14-beta.0", + "rax": "1.1.2", + "driver-universal": "^3.1.3", + "rax-use-router": "^3.0.0", + "history": "^4.10.1" + }, + "devDependencies": { + "@alib/build-scripts": "^0.1.18", + "build-plugin-rax-component": "^0.2.0" + }, + "publishConfig": { + "registry": "https://registry.npm.alibaba-inc.com" + } +} diff --git a/packages/rax-provider/src/index.js b/packages/rax-provider/src/index.js new file mode 100644 index 000000000..e51321b80 --- /dev/null +++ b/packages/rax-provider/src/index.js @@ -0,0 +1,5 @@ +import RaxProvider from './provider'; +import getRouter from './router'; + +export { getRouter }; +export default RaxProvider; diff --git a/packages/rax-provider/src/lazy-component.js b/packages/rax-provider/src/lazy-component.js new file mode 100644 index 000000000..5430c75b9 --- /dev/null +++ b/packages/rax-provider/src/lazy-component.js @@ -0,0 +1,28 @@ +import { createElement, useState, useEffect } from 'rax'; +import { app } from '@ali/lowcode-runtime'; + +export default function LazyComponent(props) { + const [schema, setSchema] = useState(null); + + useEffect(() => { + (async () => { + const { getPageData } = props || {}; + if (getPageData && !schema) { + const data = await getPageData(); + setSchema(data); + } + })(); + }); + + const { getPageData, ...restProps } = props || {}; + const Renderer = app.getRenderer(); + const Loading = app.getLoading(); + if (!Renderer || !schema) { + if (!Loading) { + return null; + } + // loading扩展点 + return createElement(Loading); + } + return createElement(Renderer, { schema, loading: Loading ? createElement(Loading) : null, ...restProps }); +} diff --git a/packages/rax-provider/src/provider.js b/packages/rax-provider/src/provider.js new file mode 100644 index 000000000..b64b2316e --- /dev/null +++ b/packages/rax-provider/src/provider.js @@ -0,0 +1,103 @@ +import { createElement, render } from 'rax'; +import UniversalDriver from 'driver-universal'; +import { app, Provider } from '@ali/lowcode-runtime'; +import LazyComponent from './lazy-component'; +import getRouter from './router'; + +export default class RaxProvider extends Provider { + // 定制构造根组件的逻辑,如切换路由机制 + createApp() { + const RouterView = this.getRouterView(); + let App; + const layoutConfig = this.getLayoutConfig(); + if (!layoutConfig || !layoutConfig.componentName) { + App = (props) => (RouterView ? createElement(RouterView, { ...props }) : null); + return App; + } + const { componentName: layoutName, props: layoutProps } = layoutConfig; + const { content: Layout, props: extraLayoutProps } = app.getLayout(layoutName) || {}; + const sectionalRender = this.isSectionalRender(); + if (!sectionalRender && Layout) { + App = (props) => + createElement( + Layout, + { ...layoutProps, ...extraLayoutProps }, + RouterView ? createElement(RouterView, props) : null, + ); + } else { + App = (props) => (RouterView ? createElement(RouterView, props) : null); + } + return App; + } + + runApp(App, config) { + render(createElement(App), document.getElementById(config?.containerId || 'App'), { driver: UniversalDriver }); + } + + // 内置实现 for 动态化渲染 + getRouterView() { + const routerConfig = this.getRouterConfig(); + if (!routerConfig) { + return null; + } + const routes = []; + let homePageId = this.getHomePage(); + Object.keys(routerConfig).forEach((pageId, idx) => { + if (!pageId) { + return; + } + const path = routerConfig[pageId]; + routes.push({ + path, + component: (props: any) => + this.getLazyComponent(pageId, { + components: this.getComponents(), + utils: this.getUtils(), + componentsMap: this.getComponentsMapObj(), + ...props, + }), + }); + if (homePageId) { + return; + } + if (idx === 0 || path === '/') { + homePageId = pageId; + } + }); + if (homePageId) { + routes.push({ + path: '**', + component: (props) => + this.getLazyComponent(homePageId, { + components: this.getComponents(), + utils: this.getUtils(), + componentsMap: this.getComponentsMapObj(), + ...props, + }), + }); + } + const Router = getRouter({ + history: this.getHistory(), + routes, + }); + const RouterView = (props) => createElement(Router, props); + return RouterView; + } + + getLazyComponent(pageId, props) { + if (!pageId) { + return null; + } + if (this.getlazyElement(pageId)) { + return this.getlazyElement(pageId); + } + const lazyElement = createElement(LazyComponent, { + // eslint-disable-next-line no-return-await + getPageData: async () => await this.getPageData(pageId), + key: pageId, + ...props, + }); + this.setlazyElement(pageId, lazyElement); + return lazyElement; + } +} diff --git a/packages/rax-provider/src/router.js b/packages/rax-provider/src/router.js new file mode 100644 index 000000000..5f2ffaba4 --- /dev/null +++ b/packages/rax-provider/src/router.js @@ -0,0 +1,26 @@ +import { useRouter } from 'rax-use-router'; +import { createHashHistory, createBrowserHistory } from 'history'; + +const getConfig = (config) => { + let { history } = config; + const { routes } = config; + if (typeof history === 'string') { + if (history === 'hash') { + history = createHashHistory(); + } else if (history === 'browser') { + history = createBrowserHistory(); + } + } + return () => ({ + history, + routes, + }); +}; + +export default function getRouter(config) { + return function Router() { + const configWrapper = getConfig(config); + const { component } = useRouter(configWrapper); + return component; + }; +} From 245e352ce34c43860131efe133e13c5a05549a16 Mon Sep 17 00:00:00 2001 From: "zude.hzd" Date: Sun, 24 May 2020 11:14:08 +0800 Subject: [PATCH 07/19] =?UTF-8?q?=E8=B5=84=E6=BA=90=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/demo/src/editor/config.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/demo/src/editor/config.js b/packages/demo/src/editor/config.js index 5e61d7054..2f0a2961e 100644 --- a/packages/demo/src/editor/config.js +++ b/packages/demo/src/editor/config.js @@ -68,10 +68,15 @@ export default { description: '资源面板', panelProps: { floatable: true, - defaultWidth: 500, + height: 300, + help: undefined, + hideTitleBar: true, + maxHeight: 800, + maxWidth: 1200, + title: "动作面板", + width: 600 }, - }, - pluginProps: {}, + } }, { pluginKey: 'zhEn', @@ -82,7 +87,6 @@ export default { pluginProps: {}, }, ], - /* centerArea: [ { pluginKey: 'eventBindDialog', @@ -90,7 +94,7 @@ export default { { pluginKey: 'variableBindDialog', }, - ],*/ + ] }, shortCuts: [], lifeCycles: { From d3fa0ea9a497a4472cec6e9666900ae4dfca1e06 Mon Sep 17 00:00:00 2001 From: "zude.hzd" Date: Sun, 24 May 2020 15:46:02 +0800 Subject: [PATCH 08/19] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E5=A4=9A=E4=B8=AA=E5=AE=9E=E4=BE=8B=E5=86=B2?= =?UTF-8?q?=E7=AA=81=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/plugin-source-editor/src/index.scss | 14 +- packages/plugin-source-editor/src/index.tsx | 142 ++++++++++--------- 2 files changed, 88 insertions(+), 68 deletions(-) diff --git a/packages/plugin-source-editor/src/index.scss b/packages/plugin-source-editor/src/index.scss index bd570e930..2297b8e6f 100644 --- a/packages/plugin-source-editor/src/index.scss +++ b/packages/plugin-source-editor/src/index.scss @@ -1,6 +1,6 @@ .source-editor-container{ height: 100%; - + position: relative; .next-tabs { -webkit-box-sizing: border-box; @@ -13,6 +13,18 @@ height: 100%; } + .editor-context-container{ + height: 100%; + width: 100%; + position: absolute; + top: 35px; + + } + + .editor-context{ + height: 100%; + } + .next-tabs-tabpane.active { visibility: visible; opacity: 1; diff --git a/packages/plugin-source-editor/src/index.tsx b/packages/plugin-source-editor/src/index.tsx index 7db14fce4..6a09e1138 100644 --- a/packages/plugin-source-editor/src/index.tsx +++ b/packages/plugin-source-editor/src/index.tsx @@ -1,9 +1,10 @@ import { Component, isValidElement, ReactElement, ReactNode } from 'react'; import { Tab, Search, Input, Button } from '@alifd/next'; -import {Editor} from '@ali/lowcode-editor-core'; +import { Editor } from '@ali/lowcode-editor-core'; import { js_beautify, css_beautify } from 'js-beautify'; import MonacoEditor from 'react-monaco-editor'; import { Designer } from '@ali/lowcode-designer'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.main.js'; const TAB_KEY = { JS_TAB: 'js_tab', CSS_TAB: 'css_tab', @@ -45,8 +46,10 @@ export default class SourceEditor extends Component<{ editor: Editor; }> { private monocoEditor: Object; + private monocoEditorCss: Object; private editorCmd: Object; - private editorRef = React.createRef(); + private editorJsRef = React.createRef(); + private editorCssRef = React.createRef(); private editorNode: Object; private editorParentNode: Object; @@ -64,12 +67,12 @@ export default class SourceEditor extends Component<{ isShow: true, }); - - setTimeout(()=>{ - this.editorNode = this.editorRef.current; //记录当前dom节点; - this.editorParentNode = this.editorNode.parentNode; //记录父节点; - console.log(this.editorNode); - },0) + // setTimeout(() => { + // this.editorNode = this.editorCssRef.current; //记录当前dom节点; + // debugger + // this.editorParentNode = this.editorNode.parentNode; //记录父节点; + // console.log(this.editorNode); + // }, 0); } }); @@ -86,47 +89,44 @@ export default class SourceEditor extends Component<{ }); //editor.once('designer.mount', (designer: Designer) => { - // let schema = designer.project.getSchema(); - // mock data - let schema = { - componentTree: [ - { - state: { - // 初始state: 选填 对象类型/变量表达式 - btnText: 'submit', // 默认数据值: 选填 变量表达式 + // let schema = designer.project.getSchema(); + // mock data + let schema = { + componentTree: [ + { + state: { + // 初始state: 选填 对象类型/变量表达式 + btnText: 'submit', // 默认数据值: 选填 变量表达式 + }, + css: 'body {font-size: 12px;} .botton{widht:100px;color:#ff00ff}', //css样式描述: 选填 + lifeCycles: { + //生命周期: 选填 对象类型 + didMount: { + type: 'JSExpression', + value: "function() {\n \t\tconsole.log('did mount');\n\t}", }, - css: 'body {font-size: 12px;} .botton{widht:100px;color:#ff00ff}', //css样式描述: 选填 - lifeCycles: { - //生命周期: 选填 对象类型 - didMount: { - type: 'JSExpression', - value: "function() {\n \t\tconsole.log('did mount');\n\t}", - }, - willUnmount: { - type: 'JSExpression', - value: "function() {\n \t\tconsole.log('will umount');\n\t}", - }, - }, - methods: { - //自定义方法对象: 选填 对象类型 - getData: { - //自定义方法: 选填 函数类型 - type: 'JSExpression', - value: "function() {\n \t\tconsole.log('testFunc');\n \t}", - }, + willUnmount: { + type: 'JSExpression', + value: "function() {\n \t\tconsole.log('will umount');\n\t}", }, }, - ], - }; + methods: { + //自定义方法对象: 选填 对象类型 + getData: { + //自定义方法: 选填 函数类型 + type: 'JSExpression', + value: "function() {\n \t\tconsole.log('testFunc');\n \t}", + }, + }, + }, + ], + }; - this.initCode(schema); + this.initCode(schema); //}); } - componentDidMount(){ - - - } + componentDidMount() {} openPluginPannel = () => { const { editor } = this.props; @@ -224,17 +224,6 @@ export default class SourceEditor extends Component<{ } editorDidMount = (editor, monaco) => { - console.log('editorDidMount', editor); - - // var commandId = editor.addCommand( - // 0, - // function() { - // // services available in `ctx` - // alert('my command is executing!'); - // }, - // '', - // ); - if (this.state.selectTab == TAB_KEY.JS_TAB) { this.monocoEditor = editor; } @@ -248,6 +237,14 @@ export default class SourceEditor extends Component<{ this.setState({ selectTab: key, }); + + if (key === TAB_KEY.JS_TAB) { + document.getElementById('cssEditorDom').setAttribute('style', 'display:none'); + document.getElementById('jsEditorDom').setAttribute('style', 'block'); + } else { + document.getElementById('jsEditorDom').setAttribute('style', 'display:none'); + document.getElementById('cssEditorDom').setAttribute('style', 'block'); + } }; updateCode = (newCode) => { @@ -273,25 +270,36 @@ export default class SourceEditor extends Component<{ ]; return ( -
+
{tabs.map((item) => ( - - {isShow && ( -
- this.updateCode(newCode)} - editorDidMount={(editor, monaco) => this.editorDidMount.call(this, editor, monaco)} - /> -
- )} -
+ ))}
+ {isShow && ( +
+
+ this.updateCode(newCode)} + editorDidMount={(editor, monaco) => this.editorDidMount.call(this, editor, monaco)} + /> +
+
+ this.updateCode(newCode)} + editorDidMount={(editor, monaco) => this.editorDidMount.call(this, editor, monaco)} + /> +
+
+ )} +
From d9a59411e472253b435e71980b8f49cda7823963 Mon Sep 17 00:00:00 2001 From: "wuji.xwt" Date: Mon, 25 May 2020 12:07:50 +0800 Subject: [PATCH 09/19] chore: import lowcode-editor-skeleton --- packages/editor-preset-general/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/editor-preset-general/src/index.ts b/packages/editor-preset-general/src/index.ts index 79f0dcc48..cc07747d6 100644 --- a/packages/editor-preset-general/src/index.ts +++ b/packages/editor-preset-general/src/index.ts @@ -1,8 +1,7 @@ import { render } from 'react-dom'; import { createElement } from 'react'; -import { Workbench } from '@ali/lowcode-editor-skeleton'; +import { Workbench, Skeleton, SettingsPrimaryPane } from '@ali/lowcode-editor-skeleton'; import { globalContext, Editor } from '@ali/lowcode-editor-core'; -import { Skeleton, SettingsPrimaryPane } from '@ali/lowcode-editor-skeleton'; import { Designer } from '@ali/lowcode-designer'; import { OutlineBackupPane, getTreeMaster } from '@ali/lowcode-plugin-outline-pane'; import DesignerPlugin from '@ali/lowcode-plugin-designer'; From 68aef30da3cbbd8b367f801e6909ec1d3287a37e Mon Sep 17 00:00:00 2001 From: "wuji.xwt" Date: Mon, 25 May 2020 17:28:49 +0800 Subject: [PATCH 10/19] chore: delete unnecessary code --- packages/demo/src/editor/editor.ts | 47 ------------------------------ 1 file changed, 47 deletions(-) delete mode 100644 packages/demo/src/editor/editor.ts diff --git a/packages/demo/src/editor/editor.ts b/packages/demo/src/editor/editor.ts deleted file mode 100644 index 2a03ebaee..000000000 --- a/packages/demo/src/editor/editor.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { globalContext, Editor } from '@ali/lowcode-editor-core'; -import { Designer, addBuiltinComponentAction } from '@ali/lowcode-designer'; -import Outline from '@ali/lowcode-plugin-outline-pane'; - -import DesignerPlugin from '@ali/lowcode-plugin-designer'; -import { Skeleton, SettingsPrimaryPane } from '@ali/lowcode-editor-skeleton'; - -import { InstanceNodeSelector } from './components'; - -export const editor = new Editor(); -globalContext.register(editor, Editor); - -export const skeleton = new Skeleton(editor); -editor.set(Skeleton, skeleton); - -export const designer = new Designer({ editor: editor }); -editor.set(Designer, designer); - -skeleton.add({ - area: 'mainArea', - name: 'designer', - type: 'Widget', - content: DesignerPlugin, -}); -skeleton.add({ - area: 'rightArea', - name: 'settingsPane', - type: 'Panel', - content: SettingsPrimaryPane, -}); -skeleton.add({ - area: 'leftArea', - name: 'outlinePane', - type: 'PanelDock', - content: Outline, - panelProps: { - area: 'leftFixedArea', - }, -}); - -// 实例节点选择器,线框高亮 -addBuiltinComponentAction({ - name: 'instance-node-selector', - content: InstanceNodeSelector, - important: true, - condition: 'always', -}); From 9224e807258f729923cd04abc4742cbb95d09833 Mon Sep 17 00:00:00 2001 From: "wuji.xwt" Date: Mon, 25 May 2020 17:36:21 +0800 Subject: [PATCH 11/19] chore: icon for source --- packages/demo/src/editor/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/demo/src/editor/config.js b/packages/demo/src/editor/config.js index 2f0a2961e..153e58de1 100644 --- a/packages/demo/src/editor/config.js +++ b/packages/demo/src/editor/config.js @@ -64,7 +64,7 @@ export default { type: 'PanelIcon', props: { align: 'top', - icon: 'zujianku', + icon: 'wenjian', description: '资源面板', panelProps: { floatable: true, From 0f9033e513ee0a2310d0dd4bbf13a48b8416def6 Mon Sep 17 00:00:00 2001 From: "wuji.xwt" Date: Mon, 25 May 2020 20:05:30 +0800 Subject: [PATCH 12/19] chore: remove unnecessary dependencies --- packages/editor-preset-general/package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/editor-preset-general/package.json b/packages/editor-preset-general/package.json index 6323eac30..5996e04e8 100644 --- a/packages/editor-preset-general/package.json +++ b/packages/editor-preset-general/package.json @@ -23,14 +23,11 @@ "@alifd/next": "^1.19.12", "@alife/theme-lowcode-dark": "^0.1.0", "@alife/theme-lowcode-light": "^0.1.0", - "domready": "^1.0.8", - "immutable": "^3.8.1", "react": "^16.8.1", "react-dom": "^16.8.1" }, "devDependencies": { "@alib/build-scripts": "^0.1.18", - "@types/domready": "^1.0.0", "@types/events": "^3.0.0", "@types/react": "^16.8.3", "@types/react-dom": "^16.8.2", From 5db0c126eb723010646a336bf7115de49e5fc669 Mon Sep 17 00:00:00 2001 From: "wuyue.xht" Date: Fri, 29 May 2020 13:29:14 +0800 Subject: [PATCH 13/19] =?UTF-8?q?chore:=20=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/runtime/package.json | 2 +- packages/runtime/src/core/provider.ts | 2 +- packages/runtime/src/core/runApp.ts | 22 ---------------------- 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 276e1cc32..095b2e785 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@ali/lowcode-runtime", - "version": "0.8.14", + "version": "0.8.15", "description": "Runtime for Ali lowCode engine", "files": [ "es", diff --git a/packages/runtime/src/core/provider.ts b/packages/runtime/src/core/provider.ts index 907dd4903..fc116e4ef 100644 --- a/packages/runtime/src/core/provider.ts +++ b/packages/runtime/src/core/provider.ts @@ -182,7 +182,7 @@ export default class Provider implements IProvider { } getAppData(): any { - throw new Error('Method called "getPageData" not implemented.'); + throw new Error('Method called "getAppData" not implemented.'); } getPageData(pageId?: string): any { diff --git a/packages/runtime/src/core/runApp.ts b/packages/runtime/src/core/runApp.ts index 6fb287f99..3dea9fb92 100644 --- a/packages/runtime/src/core/runApp.ts +++ b/packages/runtime/src/core/runApp.ts @@ -23,28 +23,6 @@ export interface IAppConfig { [key: string]: any; } -// export interface IRecoreAppConfig { -// history?: HistoryMode; -// globalComponents?: IComponents; -// globalUtils?: IUtils; -// containerId?: string; -// } - -// function transformConfig(config: IAppConfig | (() => IAppConfig)): IRecoreAppConfig { -// if (!config) { -// return {}; -// } -// if (typeof config === 'function') { -// config = config(); -// } -// return { -// history: config.history, -// globalComponents: config.components, -// globalUtils: config.utils, -// containerId: config.containerId, -// }; -// } - export default function runApp() { const provider = app.getProvider(); if (!provider) { From 21d4f64780d1c831f53b9a8eb2e6d6194e3808e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8A=9B=E7=9A=93?= Date: Wed, 17 Jun 2020 15:23:51 +0800 Subject: [PATCH 14/19] =?UTF-8?q?fix:=20=E6=A0=B9=E6=8D=AE=E7=9B=AE?= =?UTF-8?q?=E6=A0=87=E5=85=83=E7=B4=A0=E7=9A=84canDropIn=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E6=98=AF=E5=90=A6=E8=83=BD=E6=94=BE=E5=85=A5?= =?UTF-8?q?=E5=85=B6=E4=BB=96=E5=85=83=E7=B4=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/designer/src/builtin-simulator/host.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index 6111977f7..a29ae0156 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.ts @@ -787,7 +787,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost Date: Wed, 17 Jun 2020 15:34:31 +0800 Subject: [PATCH 15/19] =?UTF-8?q?fix:=20=E6=9B=B4=E6=94=B9=E7=94=9F?= =?UTF-8?q?=E6=88=90=20id=20=E7=9A=84=E8=A7=84=E5=88=99,=20=E5=90=A6?= =?UTF-8?q?=E5=88=99=E5=91=BD=E4=B8=AD=20recore=20=E8=A7=A3=E6=9E=90=20id?= =?UTF-8?q?=20=E7=9A=84=E4=B8=80=E4=B8=AA=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code-generator/src/demo/simpleDemo.ts | 24 ++++---- packages/demo/public/schema.json | 58 +++++++++---------- packages/designer/src/document/node/node.ts | 2 +- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/packages/code-generator/src/demo/simpleDemo.ts b/packages/code-generator/src/demo/simpleDemo.ts index cf124ceb9..2e1f28a76 100644 --- a/packages/code-generator/src/demo/simpleDemo.ts +++ b/packages/code-generator/src/demo/simpleDemo.ts @@ -58,7 +58,7 @@ const demoData: IProjectSchema = { componentsTree: [ { componentName: 'Page', - id: 'node$1', + id: 'node_1', meta: { title: '测试', router: '/', @@ -74,7 +74,7 @@ const demoData: IProjectSchema = { children: [ { componentName: 'Form', - id: 'node$2', + id: 'node_2', props: { labelCol: 4, style: {}, @@ -83,7 +83,7 @@ const demoData: IProjectSchema = { children: [ { componentName: 'Form.Item', - id: 'node$3', + id: 'node_3', props: { label: '姓名:', name: 'name', @@ -92,7 +92,7 @@ const demoData: IProjectSchema = { children: [ { componentName: 'Input', - id: 'node$4', + id: 'node_4', props: { placeholder: '请输入', size: 'medium', @@ -105,7 +105,7 @@ const demoData: IProjectSchema = { }, { componentName: 'Form.Item', - id: 'node$5', + id: 'node_5', props: { label: '年龄:', name: 'age', @@ -114,7 +114,7 @@ const demoData: IProjectSchema = { children: [ { componentName: 'NumberPicker', - id: 'node$6', + id: 'node_6', props: { size: 'medium', type: 'normal', @@ -124,7 +124,7 @@ const demoData: IProjectSchema = { }, { componentName: 'Form.Item', - id: 'node$7', + id: 'node_7', props: { label: '职业:', name: 'profession', @@ -132,7 +132,7 @@ const demoData: IProjectSchema = { children: [ { componentName: 'Select', - id: 'node$8', + id: 'node_8', props: { dataSource: [ { @@ -154,7 +154,7 @@ const demoData: IProjectSchema = { }, { componentName: 'Div', - id: 'node$9', + id: 'node_9', props: { style: { textAlign: 'center', @@ -163,12 +163,12 @@ const demoData: IProjectSchema = { children: [ { componentName: 'Button.Group', - id: 'node$a', + id: 'node_a', props: {}, children: [ { componentName: 'Button', - id: 'node$b', + id: 'node_b', props: { type: 'primary', style: { @@ -180,7 +180,7 @@ const demoData: IProjectSchema = { }, { componentName: 'Button', - id: 'node$d', + id: 'node_d', props: { type: 'normal', style: { diff --git a/packages/demo/public/schema.json b/packages/demo/public/schema.json index e3de976e0..de5926577 100644 --- a/packages/demo/public/schema.json +++ b/packages/demo/public/schema.json @@ -1,6 +1,6 @@ { "componentName": "Page", - "id": "node$1", + "id": "node_1", "props": { "ref": "outterView", "autoLoading": true, @@ -23,7 +23,7 @@ "children": [ { "componentName": "Steps", - "id": "node$1i", + "id": "node_1i", "props": { "dataSource": [ { @@ -87,7 +87,7 @@ }, { "componentName": "Title", - "id": "node$b", + "id": "node_b", "props": { "text": "请填写以下人员信息表单", "type": "primary", @@ -98,7 +98,7 @@ }, { "componentName": "Paragraph", - "id": "node$e", + "id": "node_e", "props": { "content": "人最宝贵的是生命。它给予我们只有一次。人的一生应当这样度过:当他回首往事时不因虚度年华而悔恨,也不因碌碌无为而羞耻。这样在他临死的时侯就能够说:我已把我整个的生命和全部精力都献给最壮丽的事业——为人类的解放而斗争。", "size": "medium", @@ -109,7 +109,7 @@ }, { "componentName": "ColumnsLayout", - "id": "node$r", + "id": "node_r", "props": { "layout": "6:6", "columnGap": "16px", @@ -122,7 +122,7 @@ "children": [ { "componentName": "Column", - "id": "node$s", + "id": "node_s", "props": { "fieldId": "column_kadcb0o9", "__style__": {} @@ -130,7 +130,7 @@ "children": [ { "componentName": "Card", - "id": "node$n", + "id": "node_n", "props": { "title": "基本信息", "subTitle": { @@ -144,7 +144,7 @@ "value": [ { "componentName": "Icon", - "id": "node$q", + "id": "node_q", "props": { "type": { "useType": true, @@ -168,12 +168,12 @@ "children": [ { "componentName": "CardContent", - "id": "node$o", + "id": "node_o", "props": {}, "children": [ { "componentName": "Form", - "id": "node$f", + "id": "node_f", "props": { "labelAlign": "top", "size": "medium", @@ -190,7 +190,7 @@ "children": [ { "componentName": "TextField", - "id": "node$g", + "id": "node_g", "props": { "__category__": "form", "__useMediator": "value", @@ -256,7 +256,7 @@ }, { "componentName": "RadioField", - "id": "node$14", + "id": "node_14", "props": { "__category__": "form", "__useMediator": "value", @@ -324,7 +324,7 @@ }, { "componentName": "SelectField", - "id": "node$h", + "id": "node_h", "props": { "__category__": "form", "__useMediator": "value", @@ -404,7 +404,7 @@ }, { "componentName": "Div", - "id": "node$i", + "id": "node_i", "props": { "behavior": "NORMAL", "__style__": {}, @@ -413,7 +413,7 @@ "children": [ { "componentName": "Button", - "id": "node$j", + "id": "node_j", "props": { "content": { "type": "i18n", @@ -449,7 +449,7 @@ }, { "componentName": "Button", - "id": "node$k", + "id": "node_k", "props": { "content": { "type": "i18n", @@ -495,7 +495,7 @@ }, { "componentName": "Column", - "id": "node$t", + "id": "node_t", "props": { "fieldId": "column_kadcb0oa", "__style__": {} @@ -503,7 +503,7 @@ "children": [ { "componentName": "Card", - "id": "node$u", + "id": "node_u", "props": { "title": { "type": "JSSlot", @@ -511,7 +511,7 @@ "value": [ { "componentName": "Icon", - "id": "node$18", + "id": "node_18", "props": { "type": { "useType": true, @@ -525,7 +525,7 @@ }, { "componentName": "Link", - "id": "node$19", + "id": "node_19", "props": { "content": { "type": "i18n", @@ -559,7 +559,7 @@ "value": [ { "componentName": "Icon", - "id": "node$1a", + "id": "node_1a", "props": { "type": { "useType": true, @@ -583,12 +583,12 @@ "children": [ { "componentName": "CardContent", - "id": "node$v", + "id": "node_v", "props": {}, "children": [ { "componentName": "Form", - "id": "node$x", + "id": "node_x", "props": { "labelAlign": "top", "size": "medium", @@ -605,7 +605,7 @@ "children": [ { "componentName": "TextField", - "id": "node$y", + "id": "node_y", "props": { "__category__": "form", "__useMediator": "value", @@ -671,7 +671,7 @@ }, { "componentName": "DateField", - "id": "node$15", + "id": "node_15", "props": { "__category__": "form", "__useMediator": "value", @@ -716,7 +716,7 @@ }, { "componentName": "SelectField", - "id": "node$z", + "id": "node_z", "props": { "__category__": "form", "__useMediator": "value", @@ -796,7 +796,7 @@ }, { "componentName": "Div", - "id": "node$10", + "id": "node_10", "props": { "behavior": "NORMAL", "__style__": {}, @@ -805,7 +805,7 @@ "children": [ { "componentName": "Button", - "id": "node$11", + "id": "node_11", "props": { "content": { "type": "i18n", @@ -841,7 +841,7 @@ }, { "componentName": "Button", - "id": "node$12", + "id": "node_12", "props": { "content": { "type": "i18n", diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index 3ec49e595..cad0cfe28 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -144,7 +144,7 @@ export class Node { constructor(readonly document: DocumentModel, nodeSchema: Schema) { const { componentName, id, children, props, ...extras } = nodeSchema; - this.id = id || `node$${document.nextId()}`; + this.id = id || `node_${document.nextId()}`; this.componentName = componentName; if (this.componentName === 'Leaf') { this.props = new Props(this, { From d44f95b643d5622cc123cc0b0d631c9eb938e376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8A=9B=E7=9A=93?= Date: Thu, 18 Jun 2020 14:46:39 +0800 Subject: [PATCH 16/19] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20node=20repla?= =?UTF-8?q?ceWith=20=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/document/node/node-children.ts | 7 ++++ packages/designer/src/document/node/node.ts | 35 +++++++++++++++---- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/packages/designer/src/document/node/node-children.ts b/packages/designer/src/document/node/node-children.ts index ec038685b..68026e831 100644 --- a/packages/designer/src/document/node/node-children.ts +++ b/packages/designer/src/document/node/node-children.ts @@ -158,6 +158,13 @@ export class NodeChildren { return this.children.indexOf(node); } + /** + * + */ + splice(start: number, deleteCount: number, node: Node): Node[] { + return this.children.splice(start, deleteCount, node); + } + /** * 根据索引获得节点 */ diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index cad0cfe28..9dd21f6f8 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -175,9 +175,9 @@ export class Node { if (!autoruns || autoruns.length < 1) { return; } - this.autoruns = autoruns.map(item => { + this.autoruns = autoruns.map((item) => { return autorun(() => { - item.autorun(this.props.get(item.name, true) as any) + item.autorun(this.props.get(item.name, true) as any); }, true); }); } @@ -384,9 +384,33 @@ export class Node { // todo } - replaceWith(schema: Schema, migrate = true) { + replaceWith(schema: Schema, migrate = false) { // reuse the same id? or replaceSelection - // + schema = Object.assign({}, migrate ? this.export() : {}, schema); + return this.parent?.replaceChild(this, schema); + } + + /** + * 替换子节点 + * + * @param {Node} node + * @param {object} data + */ + replaceChild(node: Node, data: any) { + if (this.children?.has(node)) { + const selected = this.document.selection.has(node.id); + + delete data.id; + const newNode = this.document.createNode(data); + + this.insertBefore(newNode, node); + node.remove(); + + if (selected) { + this.document.selection.select(newNode.id); + } + } + return node; } getProp(path: string, stash = true): Prop | null { @@ -442,7 +466,6 @@ export class Node { return this.parent.children.indexOf(this); } - /** * 获取下一个兄弟节点 */ @@ -607,7 +630,7 @@ export class Node { if (this.isParental()) { this.children.purge(); } - this.autoruns?.forEach(dispose => dispose()); + this.autoruns?.forEach((dispose) => dispose()); this.props.purge(); this.document.internalRemoveAndPurgeNode(this); } From 7167767f293fdc6c8d091b05c7ce211d18201038 Mon Sep 17 00:00:00 2001 From: "wuji.xwt" Date: Sun, 21 Jun 2020 17:11:54 +0800 Subject: [PATCH 17/19] feat: init rax-render --- README.md | 2 +- packages/rax-render/README.md | 49 ++ packages/rax-render/build.json | 11 + packages/rax-render/package.json | 52 ++ .../rax-render/src/comp/visualDom/index.css | 19 + .../rax-render/src/comp/visualDom/index.jsx | 23 + packages/rax-render/src/context/appContext.js | 4 + packages/rax-render/src/engine/base.jsx | 512 ++++++++++++++++++ .../rax-render/src/engine/blockEngine.jsx | 83 +++ packages/rax-render/src/engine/compEngine.jsx | 103 ++++ packages/rax-render/src/engine/index.jsx | 122 +++++ packages/rax-render/src/engine/pageEngine.jsx | 90 +++ packages/rax-render/src/engine/tempEngine.jsx | 67 +++ packages/rax-render/src/hoc/compFactory.js | 71 +++ packages/rax-render/src/hoc/compWrapper.js | 15 + packages/rax-render/src/index.jsx | 5 + 16 files changed, 1227 insertions(+), 1 deletion(-) create mode 100644 packages/rax-render/README.md create mode 100644 packages/rax-render/build.json create mode 100644 packages/rax-render/package.json create mode 100644 packages/rax-render/src/comp/visualDom/index.css create mode 100644 packages/rax-render/src/comp/visualDom/index.jsx create mode 100644 packages/rax-render/src/context/appContext.js create mode 100644 packages/rax-render/src/engine/base.jsx create mode 100644 packages/rax-render/src/engine/blockEngine.jsx create mode 100644 packages/rax-render/src/engine/compEngine.jsx create mode 100644 packages/rax-render/src/engine/index.jsx create mode 100644 packages/rax-render/src/engine/pageEngine.jsx create mode 100644 packages/rax-render/src/engine/tempEngine.jsx create mode 100644 packages/rax-render/src/hoc/compFactory.js create mode 100644 packages/rax-render/src/hoc/compWrapper.js create mode 100644 packages/rax-render/src/index.jsx diff --git a/README.md b/README.md index 3007afc49..34ff152c5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ #### 创建新包: -- `./create.sh ` +- `./scripts/create.sh ` #### 跑起来: diff --git a/packages/rax-render/README.md b/packages/rax-render/README.md new file mode 100644 index 000000000..3d66912a5 --- /dev/null +++ b/packages/rax-render/README.md @@ -0,0 +1,49 @@ +# Rax Renderer + +Rax 渲染模块。 + +## 安装 + +``` +$ npm install @ali/lowcode-engine-rax-renderer --save +``` + +## 使用 + +```js +import { createElement, render } from 'rax'; +import DriverUniversal from 'driver-universal'; +import RaxRenderer from '@ali/lowcode-engine-rax-renderer'; + +const components = { + View, + Text +}; + +const schema = { + componentName: 'Page', + fileName: 'home', + children: [ + { + componentName: 'View', + children: [ + { + componentName: 'Text', + props: { + type: 'primary' + }, + children: ['Welcome to Your Rax App'] + } + ] + } + ] +}; + +render( + , + document.getElementById('root'), { driver: DriverUniversal } +); +``` diff --git a/packages/rax-render/build.json b/packages/rax-render/build.json new file mode 100644 index 000000000..3edf14380 --- /dev/null +++ b/packages/rax-render/build.json @@ -0,0 +1,11 @@ +{ + "plugins": [ + [ + "build-plugin-rax-component", + { + "type": "rax", + "targets": ["web"] + } + ] + ] +} diff --git a/packages/rax-render/package.json b/packages/rax-render/package.json new file mode 100644 index 000000000..90d0070ba --- /dev/null +++ b/packages/rax-render/package.json @@ -0,0 +1,52 @@ +{ + "name": "@ali/lowcode-engine-rax-renderer", + "version": "0.1.0", + "description": "Rax renderer for Ali lowCode engine", + "main": "lib/index.js", + "module": "lib/index.js", + "miniappConfig": { + "main": "lib/miniapp/index", + "main:wechat": "lib/wechat-miniprogram/index" + }, + "files": [ + "dist", + "es", + "lib", + "src", + "types" + ], + "keywords": [ + "low-code", + "lowcode", + "Rax" + ], + "engines": { + "npm": ">=3.0.0" + }, + "peerDependencies": { + "rax": "^1.1.0", + "prop-types": "^15.7.2" + }, + "scripts": { + "start": "build-scripts start", + "build": "build-scripts build", + "prepublish": "npm run build" + }, + "dependencies": { + "@ali/iceluna-sdk": "^1.0.7-beta.12", + "classnames": "^2.2.6", + "debug": "^4.1.1", + "lodash.isempty": "^4.4.0", + "rax-find-dom-node": "^1.0.1", + "rax-text": "^1.1.6", + "rax-view": "^1.0.0" + }, + "devDependencies": { + "@alib/build-scripts": "^0.1.0", + "build-plugin-rax-component": "^0.1.4" + }, + "publishConfig": { + "registry": "https://registry.npm.alibaba-inc.com" + }, + "license": "MIT" +} diff --git a/packages/rax-render/src/comp/visualDom/index.css b/packages/rax-render/src/comp/visualDom/index.css new file mode 100644 index 000000000..a9b944a7f --- /dev/null +++ b/packages/rax-render/src/comp/visualDom/index.css @@ -0,0 +1,19 @@ +.visual-dom .panel-container { + box-sizing: border-box; + border: 1px solid #e9e9e9; +} + +.visual-dom .panel-container .title { + display: block; + font-size: 12px; + color: #333; + background-color: #ebecf0; + line-height: 28px; + padding: 0 12px; + border-bottom: 1px solid #e9e9e9; +} + +.visual-dom .panel-container .content { + min-height: 20px; + padding: 5px; +} diff --git a/packages/rax-render/src/comp/visualDom/index.jsx b/packages/rax-render/src/comp/visualDom/index.jsx new file mode 100644 index 000000000..7d480098a --- /dev/null +++ b/packages/rax-render/src/comp/visualDom/index.jsx @@ -0,0 +1,23 @@ +import { Component } from 'rax'; +import View from 'rax-view'; +import Text from 'rax-text'; +import './index.css'; + +export default class VisualDom extends Component { + static displayName = 'VisualDom'; + static defaultProps = { + children: null + }; + render() { + const { children, title, label, text, __componentName } = this.props; + + return ( + + + {title || label || text || __componentName} + {children} + + + ); + } +} diff --git a/packages/rax-render/src/context/appContext.js b/packages/rax-render/src/context/appContext.js new file mode 100644 index 000000000..b1fce29c4 --- /dev/null +++ b/packages/rax-render/src/context/appContext.js @@ -0,0 +1,4 @@ +import { createContext } from 'rax'; + +const context = createContext({}); +export default context; diff --git a/packages/rax-render/src/engine/base.jsx b/packages/rax-render/src/engine/base.jsx new file mode 100644 index 000000000..eedb51d87 --- /dev/null +++ b/packages/rax-render/src/engine/base.jsx @@ -0,0 +1,512 @@ +import { Component, createElement } from 'rax'; +import PropTypes from 'prop-types'; +import Debug from 'debug'; +import View from 'rax-view'; +import DataHelper from '@ali/iceluna-sdk/lib/utils/dataHelper'; +import { + forEach, + getValue, + parseData, + parseExpression, + isEmpty, + isSchema, + isFileSchema, + isJSExpression, + isJSSlot, + isJSFunction, + transformArrayToMap, + checkPropTypes, + generateI18n, + acceptsRef, +} from '@ali/iceluna-sdk/lib/utils'; +import VisualDom from '../comp/visualDom'; +import AppContext from '../context/appContext'; +import CompWrapper from '../hoc/compWrapper'; + +const debug = Debug('engine:base'); +const DESIGN_MODE = { + EXTEND: 'extend', + BORDER: 'border', + PREVIEW: 'preview', +}; +const OVERLAY_LIST = ['Dialog', 'Overlay']; +let scopeIdx = 0; + +export default class BaseEngine extends Component { + static dislayName = 'base-engine'; + static propTypes = { + locale: PropTypes.string, + messages: PropTypes.object, + __appHelper: PropTypes.object, + __components: PropTypes.object, + __componentsMap: PropTypes.object, + __ctx: PropTypes.object, + __schema: PropTypes.object + }; + static defaultProps = { + __schema: {} + }; + static contextType = AppContext; + + constructor(props, context) { + super(props, context); + this.appHelper = props.__appHelper; + this.__compScopes = {}; + const { locale, messages } = props; + this.i18n = generateI18n(locale, messages); + this.__bindCustomMethods(props); + } + + async getSnapshotBeforeUpdate() { + this.__setLifeCycleMethods('getSnapshotBeforeUpdate', arguments); + } + + async componentDidMount() { + this.reloadDataSource(); + this.__setLifeCycleMethods('componentDidMount', arguments); + } + + async componentDidUpdate() { + this.__setLifeCycleMethods('componentDidUpdate', arguments); + } + + async componentWillUnmount() { + this.__setLifeCycleMethods('componentWillUnmount', arguments); + } + + async componentDidCatch(e) { + this.__setLifeCycleMethods('componentDidCatch', arguments); + console.warn(e); + } + + reloadDataSource = () => { + return new Promise((resolve, reject) => { + debug('reload data source'); + if (!this.__dataHelper) { + this.__showPlaceholder = false; + return resolve(); + } + this.__dataHelper + .getInitData() + .then(res => { + this.__showPlaceholder = false; + if (isEmpty(res)) { + this.forceUpdate(); + return resolve(); + } + this.setState(res, resolve); + }) + .catch(err => { + if (this.__showPlaceholder) { + this.__showPlaceholder = false; + this.forceUpdate(); + } + reject(err); + }); + }); + }; + + __setLifeCycleMethods = (method, args) => { + const lifeCycleMethods = getValue(this.props.__schema, 'lifeCycles', {}); + if (lifeCycleMethods[method]) { + try { + return lifeCycleMethods[method].apply(this, args); + } catch (e) { + console.error(`[${this.props.__schema.componentName}]生命周期${method}出错`, e); + } + } + }; + + __bindCustomMethods = (props = this.props) => { + const { __schema } = props; + const customMethodsList = Object.keys(__schema.methods || {}) || []; + this.__customMethodsList && + this.__customMethodsList.forEach(item => { + if (!customMethodsList.includes(item)) { + delete this[item]; + } + }); + this.__customMethodsList = customMethodsList; + forEach(__schema.methods, (val, key) => { + this[key] = val.bind(this); + }); + }; + + __generateCtx = ctx => { + const { pageContext, compContext } = this.context; + const obj = { + page: pageContext, + component: compContext, + ...ctx + }; + forEach(obj, (val, key) => { + this[key] = val; + }); + }; + + __parseData = (data, ctx) => { + const { __ctx } = this.props; + return parseData(data, ctx || __ctx || this); + }; + + __initDataSource = (props = this.props) => { + const schema = props.__schema || {}; + const appHelper = props.__appHelper; + const dataSource = (schema && schema.dataSource) || {}; + this.__dataHelper = new DataHelper(this, dataSource, appHelper, config => this.__parseData(config)); + this.dataSourceMap = this.__dataHelper.dataSourceMap; + // 设置容器组件占位,若设置占位则在初始异步请求完成之前用loading占位且不渲染容器组件内部内容 + this.__showPlaceholder = + this.__parseData(schema.props && schema.props.autoLoading) && + (dataSource.list || []).some(item => !!this.__parseData(item.isInit)); + }; + + __render = () => { + const schema = this.props.__schema; + this.__setLifeCycleMethods('render'); + + const engine = this.context.engine; + if (engine) { + engine.props.onCompGetCtx(schema, this); + // 画布场景才需要每次渲染bind自定义方法 + if (engine.props.designMode) { + this.__bindCustomMethods(); + this.dataSourceMap = this.__dataHelper && this.__dataHelper.updateConfig(schema.dataSource); + } + } + }; + + __getRef = ref => { + this.__ref = ref; + }; + + __createDom = () => { + const { __schema, __ctx, __components = {} } = this.props; + const self = {}; + self.__proto__ = __ctx || this; + return this.__createVirtualDom(__schema.children, self, { + schema: __schema, + Comp: __components[__schema.componentName] + }); + }; + + // 将模型结构转换成react Element + // schema 模型结构 + // self 为每个渲染组件构造的上下文,self是自上而下继承的 + // parentInfo 父组件的信息,包含schema和Comp + // idx 若为循环渲染的循环Index + __createVirtualDom = (schema, self, parentInfo, idx) => { + if (!schema) return null; + // rax text prop 兼容处理 + if (schema.componentName === 'Text') { + if (typeof schema.props.text === 'string') { + schema = Object.assign({}, schema); + schema.children = [schema.props.text]; + } + } + + const { __appHelper: appHelper, __components: components = {}, __componentsMap: componentsMap = {} } = + this.props || {}; + const { engine } = this.context || {}; + if (isJSExpression(schema)) { + return parseExpression(schema, self); + } + if (typeof schema === 'string') return schema; + if (typeof schema === 'number' || typeof schema === 'boolean') { + return schema.toString(); + } + if (Array.isArray(schema)) { + if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo); + return schema.map((item, idx) => + this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idx) + ); + } + + //解析占位组件 + if (schema.componentName === 'Flagment' && schema.children) { + let tarChildren = isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children; + return this.__createVirtualDom(tarChildren, self, parentInfo); + } + + if (schema.$$typeof) { + return schema; + } + if (!isSchema(schema)) return null; + let Comp = components[schema.componentName] || View; + + if (schema.loop !== undefined) { + return this.__createLoopVirtualDom( + { + ...schema, + loop: parseData(schema.loop, self) + }, + self, + parentInfo, + idx + ); + } + const condition = schema.condition === undefined ? true : parseData(schema.condition, self); + if (!condition) return null; + + let scopeKey = ''; + // 判断组件是否需要生成scope,且只生成一次,挂在this.__compScopes上 + if (Comp.generateScope) { + const key = parseExpression(schema.props.key, self); + if (key) { + // 如果组件自己设置key则使用组件自己的key + scopeKey = key; + } else if (!schema.__ctx) { + // 在生产环境schema没有__ctx上下文,需要手动生成一个lunaKey + schema.__ctx = { + lunaKey: `luna${++scopeIdx}` + }; + scopeKey = schema.__ctx.lunaKey; + } else { + // 需要判断循环的情况 + scopeKey = schema.__ctx.lunaKey + (idx !== undefined ? `_${idx}` : ''); + } + if (!this.__compScopes[scopeKey]) { + this.__compScopes[scopeKey] = Comp.generateScope(this, schema); + } + } + // 如果组件有设置scope,需要为组件生成一个新的scope上下文 + if (scopeKey && this.__compScopes[scopeKey]) { + const compSelf = { ...this.__compScopes[scopeKey] }; + compSelf.__proto__ = self; + self = compSelf; + } + + // 容器类组件的上下文通过props传递,避免context传递带来的嵌套问题 + const otherProps = isFileSchema(schema) + ? { + __schema: schema, + __appHelper: appHelper, + __components: components, + __componentsMap: componentsMap + } + : {}; + if (engine && engine.props.designMode) { + otherProps.__designMode = engine.props.designMode; + } + const componentInfo = componentsMap[schema.componentName] || {}; + const props = this.__parseProps(schema.props, self, '', { + schema, + Comp, + componentInfo: { + ...componentInfo, + props: transformArrayToMap(componentInfo.props, 'name') + } + }); + // 对于可以获取到ref的组件做特殊处理 + if (!acceptsRef(Comp)) { + Comp = CompWrapper(Comp); + } + otherProps.ref = ref => { + const refProps = props.ref; + if (refProps && typeof refProps === 'string') { + this[refProps] = ref; + } + engine && engine.props.onCompGetRef(schema, ref); + }; + // scope需要传入到组件上 + if (scopeKey && this.__compScopes[scopeKey]) { + props.__scope = this.__compScopes[scopeKey]; + } + if (schema.__ctx && schema.__ctx.lunaKey) { + if (!isFileSchema(schema)) { + engine && engine.props.onCompGetCtx(schema, self); + } + props.key = props.key || `${schema.__ctx.lunaKey}_${schema.__ctx.idx || 0}_${idx !== undefined ? idx : ''}`; + } else if (typeof idx === 'number' && !props.key) { + props.key = idx; + } + const renderComp = props => ( + + {(!isFileSchema(schema) && + !!schema.children && + this.__createVirtualDom( + isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children, + self, + { + schema, + Comp + } + )) || + null} + + ); + //设计模式下的特殊处理 + if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) { + //对于overlay,dialog等组件为了使其在设计模式下显示,外层需要增加一个div容器 + if (OVERLAY_LIST.includes(schema.componentName)) { + const { ref, ...overlayProps } = otherProps; + return ( +
+ {renderComp({ ...props, ...overlayProps })} +
+ ); + } + // 虚拟dom显示 + if (componentInfo && componentInfo.parentRule) { + const parentList = componentInfo.parentRule.split(','); + const { schema: parentSchema, Comp: parentComp } = parentInfo; + if (!parentList.includes(parentSchema.componentName) || parentComp !== components[parentSchema.componentName]) { + props.__componentName = schema.componentName; + Comp = VisualDom; + } else { + // 若虚拟dom在正常的渲染上下文中,就不显示设计模式了 + props.__disableDesignMode = true; + } + } + } + return renderComp({ ...props, ...otherProps }); + }; + + __createLoopVirtualDom = (schema, self, parentInfo, idx) => { + if (isFileSchema(schema)) { + console.warn('file type not support Loop'); + return null; + } + if (!Array.isArray(schema.loop)) return null; + const itemArg = (schema.loopArgs && schema.loopArgs[0]) || 'item'; + const indexArg = (schema.loopArgs && schema.loopArgs[1]) || 'index'; + return schema.loop.map((item, i) => { + const loopSelf = { + [itemArg]: item, + [indexArg]: i + }; + loopSelf.__proto__ = self; + return this.__createVirtualDom( + { + ...schema, + loop: undefined + }, + loopSelf, + parentInfo, + idx ? `${idx}_${i}` : i + ); + }); + }; + + __createContextDom = (childCtx, currCtx) => { + return ( + + {context => { + this.context = context; + this.__generateCtx(currCtx); + this.__render(); + return ( + + {this.__createDom()} + + ); + }} + + ); + }; + + __parseProps = (props, self, path, info) => { + const { schema, Comp, componentInfo = {} } = info; + const propInfo = getValue(componentInfo.props, path); + const propType = propInfo && propInfo.extra && propInfo.extra.propType; + const ignoreParse = schema.__ignoreParse || []; + const checkProps = value => { + if (!propType) return value; + return checkPropTypes(value, path, propType, componentInfo.name) ? value : undefined; + }; + + const parseReactNode = (data, params) => { + if (isEmpty(params)) { + return checkProps(this.__createVirtualDom(data, self, { schema, Comp })); + } else { + return checkProps(function() { + const args = {}; + if (Array.isArray(params) && params.length) { + params.map((item, idx) => { + if (typeof item === 'string') { + args[item] = arguments[idx]; + } else if (item && typeof item === 'object') { + args[item.name] = arguments[idx]; + } + }); + } + args.__proto__ = self; + return self.__createVirtualDom(data, args, { schema, Comp }); + }); + } + }; + + // 判断是否需要解析变量 + if ( + ignoreParse.some(item => { + if (item instanceof RegExp) { + return item.test(path); + } + return item === path; + }) + ) { + return checkProps(props); + } + if (isJSExpression(props)) { + props = parseExpression(props, self); + // 只有当变量解析出来为模型结构的时候才会继续解析 + if (!isSchema(props) && !isJSSlot(props)) return checkProps(props); + } + + if (isJSFunction(props)) { + props = props.value; + } + if (isJSSlot(props)) { + const { params, value } = props; + if (!isSchema(value) || isEmpty(value)) return undefined; + return parseReactNode(value, params); + } + // 兼容通过componentInfo判断的情况 + if (isSchema(props)) { + return parseReactNode(props); + } else if (Array.isArray(props)) { + return checkProps(props.map((item, idx) => this.__parseProps(item, self, path ? `${path}.${idx}` : idx, info))); + } else if (typeof props === 'function') { + return checkProps(props.bind(self)); + } else if (props && typeof props === 'object') { + if (props.$$typeof) return checkProps(props); + const res = {}; + forEach(props, (val, key) => { + if (key.startsWith('__')) { + res[key] = val; + return; + } + res[key] = this.__parseProps(val, self, path ? `${path}.${key}` : key, info); + }); + return checkProps(res); + } else if (typeof props === 'string') { + return checkProps(props.trim()); + } + return checkProps(props); + }; + + get utils() { + return this.appHelper && this.appHelper.utils; + } + get constants() { + return this.appHelper && this.appHelper.constants; + } + get history() { + return this.appHelper && this.appHelper.history; + } + get location() { + return this.appHelper && this.appHelper.location; + } + get match() { + return this.appHelper && this.appHelper.match; + } + + render() { + return null; + } +} diff --git a/packages/rax-render/src/engine/blockEngine.jsx b/packages/rax-render/src/engine/blockEngine.jsx new file mode 100644 index 000000000..2067d0d67 --- /dev/null +++ b/packages/rax-render/src/engine/blockEngine.jsx @@ -0,0 +1,83 @@ +import { createElement } from 'rax'; +import PropTypes from 'prop-types'; +import Debug from 'debug'; +import classnames from 'classnames'; +import { isSchema, getFileCssName } from '@ali/iceluna-sdk/lib/utils'; +import BaseEngine from './base'; + +const debug = Debug('engine:block'); + +export default class BlockEngine extends BaseEngine { + static dislayName = 'block-engine'; + static propTypes = { + __schema: PropTypes.object + }; + static defaultProps = { + __schema: {} + }; + + static getDerivedStateFromProps(props, state) { + debug(`block.getDerivedStateFromProps`); + const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps; + if (func) { + return func(props, state); + } + return null; + } + + constructor(props, context) { + super(props, context); + this.__generateCtx(); + const schema = props.__schema || {}; + this.state = this.__parseData(schema.state || {}); + this.__initDataSource(props); + this.__setLifeCycleMethods('constructor', arguments); + debug(`block.constructor - ${schema.fileName}`); + } + + async getSnapshotBeforeUpdate() { + super.getSnapshotBeforeUpdate(...arguments); + debug(`block.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`); + } + async componentDidMount() { + super.componentDidMount(...arguments); + debug(`block.componentDidMount - ${this.props.__schema.fileName}`); + } + async componentDidUpdate() { + super.componentDidUpdate(...arguments); + debug(`block.componentDidUpdate - ${this.props.__schema.fileName}`); + } + async componentWillUnmount() { + super.componentWillUnmount(...arguments); + debug(`block.componentWillUnmount - ${this.props.__schema.fileName}`); + } + async componentDidCatch() { + await super.componentDidCatch(...arguments); + debug(`block.componentDidCatch - ${this.props.__schema.fileName}`); + } + + render() { + const { __schema } = this.props; + + if (!isSchema(__schema, true) || __schema.componentName !== 'Block') { + return '区块schema结构异常!'; + } + + debug(`block.render - ${__schema.fileName}`); + + const { id, className, style } = this.__parseData(__schema.props); + + return ( +
+ {this.__createContextDom({ + blockContext: this + })} +
+ ); + } +} diff --git a/packages/rax-render/src/engine/compEngine.jsx b/packages/rax-render/src/engine/compEngine.jsx new file mode 100644 index 000000000..4a0cf54b0 --- /dev/null +++ b/packages/rax-render/src/engine/compEngine.jsx @@ -0,0 +1,103 @@ +import { createElement } from 'rax'; +import PropTypes from 'prop-types'; +import Debug from 'debug'; +import classnames from 'classnames'; +import { isSchema, getFileCssName } from '@ali/iceluna-sdk/lib/utils'; +import BaseEngine from './base'; + +const debug = Debug('engine:comp'); + +export default class CompEngine extends BaseEngine { + static dislayName = 'comp-engine'; + static propTypes = { + __schema: PropTypes.object + }; + static defaultProps = { + __schema: {} + }; + + static getDerivedStateFromProps(props, state) { + debug(`comp.getDerivedStateFromProps`); + const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps; + if (func) { + return func(props, state); + } + return null; + } + + constructor(props, context) { + super(props, context); + this.__generateCtx({ + component: this + }); + const schema = props.__schema || {}; + this.state = this.__parseData(schema.state || {}); + this.__initDataSource(props); + this.__setLifeCycleMethods('constructor', arguments); + debug(`comp.constructor - ${schema.fileName}`); + } + + async getSnapshotBeforeUpdate() { + super.getSnapshotBeforeUpdate(...arguments); + debug(`comp.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`); + } + async componentDidMount() { + super.componentDidMount(...arguments); + debug(`comp.componentDidMount - ${this.props.__schema.fileName}`); + } + async componentDidUpdate() { + super.componentDidUpdate(...arguments); + debug(`comp.componentDidUpdate - ${this.props.__schema.fileName}`); + } + async componentWillUnmount() { + super.componentWillUnmount(...arguments); + debug(`comp.componentWillUnmount - ${this.props.__schema.fileName}`); + } + async componentDidCatch(e) { + super.componentDidCatch(...arguments); + debug(`comp.componentDidCatch - ${this.props.__schema.fileName}`); + } + + render() { + const { __schema } = this.props; + + if (!isSchema(__schema, true) || __schema.componentName !== 'Component') { + return '自定义组件schema结构异常!'; + } + + debug(`comp.render - ${__schema.fileName}`); + + const { id, className, style, noContainer } = this.__parseData(__schema.props); + + if (noContainer) { + return this.__createContextDom( + { + compContext: this, + blockContext: this + }, + { + component: this + } + ); + } + + return ( +
+ {this.__createContextDom( + { + compContext: this, + blockContext: this + }, + { + component: this + } + )} +
+ ); + } +} diff --git a/packages/rax-render/src/engine/index.jsx b/packages/rax-render/src/engine/index.jsx new file mode 100644 index 000000000..e969f1a91 --- /dev/null +++ b/packages/rax-render/src/engine/index.jsx @@ -0,0 +1,122 @@ +import { Component, createElement } from 'rax'; +import PropTypes from 'prop-types'; +import Debug from 'debug'; +import * as isEmpty from 'lodash.isempty'; +import findDOMNode from 'rax-find-dom-node'; +import { isFileSchema, goldlog } from '@ali/iceluna-sdk/lib/utils'; +import AppContext from '../context/appContext'; +import Page from './pageEngine'; +import CustomComp from './compEngine'; +import Block from './blockEngine'; +import Temp from './tempEngine'; + +const debug = Debug('engine:entry'); +const ENGINE_COMPS = { + Page, + Component: CustomComp, + Block, + Temp, +}; +export default class Engine extends Component { + static dislayName = 'engine'; + static propTypes = { + appHelper: PropTypes.object, + components: PropTypes.object, + componentsMap: PropTypes.object, + designMode: PropTypes.string, + suspended: PropTypes.bool, + schema: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), + onCompGetRef: PropTypes.func, + onCompGetCtx: PropTypes.func + }; + static defaultProps = { + appHelper: null, + components: {}, + componentsMap: {}, + designMode: '', + suspended: false, + schema: {}, + onCompGetRef: () => {}, + onCompGetCtx: () => {} + }; + + constructor(props, context) { + super(props, context); + this.state = {}; + debug(`entry.constructor - ${props.schema && props.schema.componentName}`); + } + + async componentDidMount() { + goldlog( + 'EXP', + { + action: 'appear', + value: !!this.props.designMode + }, + 'engine' + ); + debug(`entry.componentDidMount - ${this.props.schema && this.props.schema.componentName}`); + } + + async componentDidUpdate() { + debug(`entry.componentDidUpdate - ${this.props.schema && this.props.schema.componentName}`); + } + + async componentWillUnmount() { + debug(`entry.componentWillUnmount - ${this.props.schema && this.props.schema.componentName}`); + } + + async componentDidCatch(e) { + console.warn(e); + } + + shouldComponentUpdate(nextProps) { + return !nextProps.suspended; + } + + __getRef = ref => { + this.__ref = ref; + if (ref) { + this.props.onCompGetRef(this.props.schema, ref, true); + } + }; + + render() { + const { schema, designMode, appHelper, components, componentsMap } = this.props; + if (isEmpty(schema)) { + return null; + } + if (!isFileSchema(schema)) { + return '模型结构异常'; + } + debug('entry.render'); + const allComponents = { ...ENGINE_COMPS, ...components }; + const Comp = allComponents[schema.componentName]; + if (Comp) { + return ( + + + + ); + } + return null; + } +} + +Engine.findDOMNode = findDOMNode; diff --git a/packages/rax-render/src/engine/pageEngine.jsx b/packages/rax-render/src/engine/pageEngine.jsx new file mode 100644 index 000000000..25eed613c --- /dev/null +++ b/packages/rax-render/src/engine/pageEngine.jsx @@ -0,0 +1,90 @@ +import { createElement } from 'rax'; +import PropTypes from 'prop-types'; +import Debug from 'debug'; +import classnames from 'classnames'; +import { isSchema, getFileCssName } from '@ali/iceluna-sdk/lib/utils'; +import BaseEngine from './base'; + +const debug = Debug('engine:page'); + +export default class PageEngine extends BaseEngine { + static dislayName = 'page-engine'; + static propTypes = { + __schema: PropTypes.object + }; + static defaultProps = { + __schema: {} + }; + + static getDerivedStateFromProps(props, state) { + debug(`page.getDerivedStateFromProps`); + const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps; + if (func) { + return func(props, state); + } + return null; + } + + constructor(props, context) { + super(props, context); + this.__generateCtx({ + page: this + }); + const schema = props.__schema || {}; + this.state = this.__parseData(schema.state || {}); + this.__initDataSource(props); + this.__setLifeCycleMethods('constructor', arguments); + + debug(`page.constructor - ${schema.fileName}`); + } + + async getSnapshotBeforeUpdate() { + super.getSnapshotBeforeUpdate(...arguments); + debug(`page.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`); + } + async componentDidMount() { + super.componentDidMount(...arguments); + debug(`page.componentDidMount - ${this.props.__schema.fileName}`); + } + async componentDidUpdate() { + super.componentDidUpdate(...arguments); + debug(`page.componentDidUpdate - ${this.props.__schema.fileName}`); + } + async componentWillUnmount() { + super.componentWillUnmount(...arguments); + debug(`page.componentWillUnmount - ${this.props.__schema.fileName}`); + } + async componentDidCatch() { + await super.componentDidCatch(...arguments); + debug(`page.componentDidCatch - ${this.props.__schema.fileName}`); + } + + render() { + const { __schema } = this.props; + if (!isSchema(__schema, true) || __schema.componentName !== 'Page') { + return '页面schema结构异常!'; + } + debug(`page.render - ${__schema.fileName}`); + + const { id, className, style } = this.__parseData(__schema.props); + + return ( +
+ {this.__createContextDom( + { + pageContext: this, + blockContext: this + }, + { + page: this + } + )} +
+ ); + } +} diff --git a/packages/rax-render/src/engine/tempEngine.jsx b/packages/rax-render/src/engine/tempEngine.jsx new file mode 100644 index 000000000..d76e01c69 --- /dev/null +++ b/packages/rax-render/src/engine/tempEngine.jsx @@ -0,0 +1,67 @@ +import { createElement } from 'rax'; +import PropTypes from 'prop-types'; +import Debug from 'debug'; +import { isSchema } from '@ali/iceluna-sdk/lib/utils'; +import AppContext from '../context/appContext'; +import BaseEngine from './base'; + +const debug = Debug('engine:temp'); +export default class TempEngine extends BaseEngine { + static dislayName = 'temp-engine'; + static propTypes = { + __ctx: PropTypes.object, + __schema: PropTypes.object + }; + static defaultProps = { + __ctx: {}, + __schema: {} + }; + + constructor(props, context) { + super(props, context); + this.state = {}; + this.cacheSetState = {}; + debug(`temp.constructor - ${props.__schema.fileName}`); + } + + componentDidMount() { + const ctx = this.props.__ctx; + if (!ctx) return; + const setState = ctx.setState; + this.cacheSetState = setState; + ctx.setState = (...args) => { + setState.call(ctx, ...args); + setTimeout(() => this.forceUpdate(), 0); + }; + debug(`temp.componentDidMount - ${this.props.__schema.fileName}`); + } + componentDidUpdate(prevProps, prevState, snapshot) { + debug(`temp.componentDidUpdate - ${this.props.__schema.fileName}`); + } + componentWillUnmount() { + const ctx = this.props.__ctx; + if (!ctx || !this.cacheSetState) return; + ctx.setState = this.cacheSetState; + delete this.cacheSetState; + debug(`temp.componentWillUnmount - ${this.props.__schema.fileName}`); + } + componentDidCatch(e) { + console.warn(e); + debug(`temp.componentDidCatch - ${this.props.__schema.fileName}`); + } + + render() { + const { __schema, __ctx } = this.props; + if (!isSchema(__schema, true) || __schema.componentName !== 'Temp') { + return '下钻编辑schema结构异常!'; + } + + debug(`temp.render - ${__schema.fileName}`); + + return ( +
+ {this.__createDom()} +
+ ); + } +} diff --git a/packages/rax-render/src/hoc/compFactory.js b/packages/rax-render/src/hoc/compFactory.js new file mode 100644 index 000000000..a28ca36a5 --- /dev/null +++ b/packages/rax-render/src/hoc/compFactory.js @@ -0,0 +1,71 @@ +import { Component, createElement } from 'rax'; +import PropTypes from 'prop-types'; +import AppHelper from '@ali/iceluna-sdk/lib/utils/appHelper'; +import { forEach, isFileSchema } from '@ali/iceluna-sdk/lib/utils'; +import CompEngine from '../engine/compEngine'; +import BlockEngine from '../engine/blockEngine'; +import AppContext from '../context/appContext'; + +export default function compFactory(schema, components = {}, componentsMap = {}, config = {}) { + // 自定义组件需要有自己独立的appHelper + const appHelper = new AppHelper(config); + class LNCompView extends Component { + static dislayName = 'luna-comp-factory'; + static version = config.version || '0.0.0'; + static contextType = AppContext; + static propTypes = { + forwardedRef: PropTypes.func + }; + render() { + if (!schema || schema.componentName !== 'Component' || !isFileSchema(schema)) { + console.warn('自定义组件模型结构异常!'); + return null; + } + const { forwardedRef, ...otherProps } = this.props; + // 低代码组件透传应用上下文 + const ctx = ['utils', 'constants', 'history', 'location', 'match']; + ctx.forEach(key => { + if (!appHelper[key] && this.context && this.context.appHelper && this.context.appHelper[key]) { + appHelper.set(key, this.context.appHelper[key]); + } + }); + // 支持通过context透传国际化配置 + const localeProps = {}; + const { locale, messages } = this.context; + if (locale && messages && messages[schema.fileName]) { + localeProps.locale = locale; + localeProps.messages = messages[schema.fileName]; + } + const props = { + ...schema.defaultProps, + ...localeProps, + ...otherProps, + __schema: schema, + ref: forwardedRef + }; + + return ( + + {context => { + this.context = context; + return ( + + ); + }} + + ); + } + } + + const ResComp = React.forwardRef((props, ref) => ); + forEach(schema.static, (val, key) => { + ResComp[key] = val; + }); + ResComp.version = config.version || '0.0.0'; + return ResComp; +} diff --git a/packages/rax-render/src/hoc/compWrapper.js b/packages/rax-render/src/hoc/compWrapper.js new file mode 100644 index 000000000..4f55a6683 --- /dev/null +++ b/packages/rax-render/src/hoc/compWrapper.js @@ -0,0 +1,15 @@ +import { createElement, Component } from 'rax'; + +export default function(Comp) { + class CompWrapper extends Component { + constructor(props, context) { + super(props, context); + } + + render() { + return ; + } + } + + return CompWrapper; +} diff --git a/packages/rax-render/src/index.jsx b/packages/rax-render/src/index.jsx new file mode 100644 index 000000000..e7b4ba078 --- /dev/null +++ b/packages/rax-render/src/index.jsx @@ -0,0 +1,5 @@ +import Engine from './engine'; + +export { default as Engine } from './engine'; +export { default as CompFactory } from './hoc/compFactory'; +export default Engine; From 7db1378fad1505791158b3641daf55a5e7becce7 Mon Sep 17 00:00:00 2001 From: "wuji.xwt" Date: Sun, 21 Jun 2020 17:47:49 +0800 Subject: [PATCH 18/19] refactor: remove iceluna-SDK --- packages/rax-render/package.json | 17 +- packages/rax-render/src/engine/base.jsx | 4 +- .../rax-render/src/engine/blockEngine.jsx | 2 +- packages/rax-render/src/engine/compEngine.jsx | 2 +- packages/rax-render/src/engine/index.jsx | 4 +- packages/rax-render/src/engine/pageEngine.jsx | 2 +- packages/rax-render/src/engine/tempEngine.jsx | 2 +- packages/rax-render/src/hoc/compFactory.js | 4 +- packages/rax-render/src/utils/appHelper.js | 49 ++ packages/rax-render/src/utils/dataHelper.js | 299 ++++++++ packages/rax-render/src/utils/index.js | 718 ++++++++++++++++++ packages/rax-render/src/utils/request.js | 171 +++++ 12 files changed, 1261 insertions(+), 13 deletions(-) create mode 100644 packages/rax-render/src/utils/appHelper.js create mode 100644 packages/rax-render/src/utils/dataHelper.js create mode 100644 packages/rax-render/src/utils/index.js create mode 100644 packages/rax-render/src/utils/request.js diff --git a/packages/rax-render/package.json b/packages/rax-render/package.json index 90d0070ba..ade1a3756 100644 --- a/packages/rax-render/package.json +++ b/packages/rax-render/package.json @@ -33,13 +33,24 @@ "prepublish": "npm run build" }, "dependencies": { - "@ali/iceluna-sdk": "^1.0.7-beta.12", + "@ali/b3-one": "^0.0.17", + "@ali/bzb-request": "2.6.1", + "@ali/lib-mtop": "^2.5.1", "classnames": "^2.2.6", "debug": "^4.1.1", - "lodash.isempty": "^4.4.0", + "events": "^3.0.0", + "fetch-jsonp": "^1.1.3", + "intl-messageformat": "^7.7.2", + "jsonuri": "^2.1.2", + "keymaster": "^1.6.2", + "lodash": "^4.17.11", + "moment": "^2.24.0", "rax-find-dom-node": "^1.0.1", "rax-text": "^1.1.6", - "rax-view": "^1.0.0" + "rax-view": "^1.0.0", + "react-is": "^16.10.1", + "serialize-javascript": "^1.7.0", + "whatwg-fetch": "^3.0.0" }, "devDependencies": { "@alib/build-scripts": "^0.1.0", diff --git a/packages/rax-render/src/engine/base.jsx b/packages/rax-render/src/engine/base.jsx index eedb51d87..95d1dcaff 100644 --- a/packages/rax-render/src/engine/base.jsx +++ b/packages/rax-render/src/engine/base.jsx @@ -2,7 +2,7 @@ import { Component, createElement } from 'rax'; import PropTypes from 'prop-types'; import Debug from 'debug'; import View from 'rax-view'; -import DataHelper from '@ali/iceluna-sdk/lib/utils/dataHelper'; +import DataHelper from '../utils/dataHelper'; import { forEach, getValue, @@ -18,7 +18,7 @@ import { checkPropTypes, generateI18n, acceptsRef, -} from '@ali/iceluna-sdk/lib/utils'; +} from '../utils'; import VisualDom from '../comp/visualDom'; import AppContext from '../context/appContext'; import CompWrapper from '../hoc/compWrapper'; diff --git a/packages/rax-render/src/engine/blockEngine.jsx b/packages/rax-render/src/engine/blockEngine.jsx index 2067d0d67..ab986cc1a 100644 --- a/packages/rax-render/src/engine/blockEngine.jsx +++ b/packages/rax-render/src/engine/blockEngine.jsx @@ -2,7 +2,7 @@ import { createElement } from 'rax'; import PropTypes from 'prop-types'; import Debug from 'debug'; import classnames from 'classnames'; -import { isSchema, getFileCssName } from '@ali/iceluna-sdk/lib/utils'; +import { isSchema, getFileCssName } from '../utils'; import BaseEngine from './base'; const debug = Debug('engine:block'); diff --git a/packages/rax-render/src/engine/compEngine.jsx b/packages/rax-render/src/engine/compEngine.jsx index 4a0cf54b0..235ad4996 100644 --- a/packages/rax-render/src/engine/compEngine.jsx +++ b/packages/rax-render/src/engine/compEngine.jsx @@ -2,7 +2,7 @@ import { createElement } from 'rax'; import PropTypes from 'prop-types'; import Debug from 'debug'; import classnames from 'classnames'; -import { isSchema, getFileCssName } from '@ali/iceluna-sdk/lib/utils'; +import { isSchema, getFileCssName } from '../utils'; import BaseEngine from './base'; const debug = Debug('engine:comp'); diff --git a/packages/rax-render/src/engine/index.jsx b/packages/rax-render/src/engine/index.jsx index e969f1a91..7bbe0fad4 100644 --- a/packages/rax-render/src/engine/index.jsx +++ b/packages/rax-render/src/engine/index.jsx @@ -1,9 +1,9 @@ import { Component, createElement } from 'rax'; import PropTypes from 'prop-types'; import Debug from 'debug'; -import * as isEmpty from 'lodash.isempty'; +import * as isEmpty from 'lodash/isEmpty'; import findDOMNode from 'rax-find-dom-node'; -import { isFileSchema, goldlog } from '@ali/iceluna-sdk/lib/utils'; +import { isFileSchema, goldlog } from '../utils'; import AppContext from '../context/appContext'; import Page from './pageEngine'; import CustomComp from './compEngine'; diff --git a/packages/rax-render/src/engine/pageEngine.jsx b/packages/rax-render/src/engine/pageEngine.jsx index 25eed613c..c09eaa265 100644 --- a/packages/rax-render/src/engine/pageEngine.jsx +++ b/packages/rax-render/src/engine/pageEngine.jsx @@ -2,7 +2,7 @@ import { createElement } from 'rax'; import PropTypes from 'prop-types'; import Debug from 'debug'; import classnames from 'classnames'; -import { isSchema, getFileCssName } from '@ali/iceluna-sdk/lib/utils'; +import { isSchema, getFileCssName } from '../utils'; import BaseEngine from './base'; const debug = Debug('engine:page'); diff --git a/packages/rax-render/src/engine/tempEngine.jsx b/packages/rax-render/src/engine/tempEngine.jsx index d76e01c69..b55436320 100644 --- a/packages/rax-render/src/engine/tempEngine.jsx +++ b/packages/rax-render/src/engine/tempEngine.jsx @@ -1,7 +1,7 @@ import { createElement } from 'rax'; import PropTypes from 'prop-types'; import Debug from 'debug'; -import { isSchema } from '@ali/iceluna-sdk/lib/utils'; +import { isSchema } from '../utils'; import AppContext from '../context/appContext'; import BaseEngine from './base'; diff --git a/packages/rax-render/src/hoc/compFactory.js b/packages/rax-render/src/hoc/compFactory.js index a28ca36a5..d56a8b5f3 100644 --- a/packages/rax-render/src/hoc/compFactory.js +++ b/packages/rax-render/src/hoc/compFactory.js @@ -1,7 +1,7 @@ import { Component, createElement } from 'rax'; import PropTypes from 'prop-types'; -import AppHelper from '@ali/iceluna-sdk/lib/utils/appHelper'; -import { forEach, isFileSchema } from '@ali/iceluna-sdk/lib/utils'; +import AppHelper from '../utils/appHelper'; +import { forEach, isFileSchema } from '../utils'; import CompEngine from '../engine/compEngine'; import BlockEngine from '../engine/blockEngine'; import AppContext from '../context/appContext'; diff --git a/packages/rax-render/src/utils/appHelper.js b/packages/rax-render/src/utils/appHelper.js new file mode 100644 index 000000000..e39741ddc --- /dev/null +++ b/packages/rax-render/src/utils/appHelper.js @@ -0,0 +1,49 @@ +import EventEmitter from 'events'; + +let instance = null; + +EventEmitter.defaultMaxListeners = 100; + +export default class AppHelper extends EventEmitter { + static getInstance = () => { + if (!instance) { + instance = new AppHelper(); + } + return instance; + }; + + constructor(config) { + super(); + instance = this; + Object.assign(this, config); + } + + get(key) { + return this[key]; + } + + set(key, val) { + if (typeof key === 'string') { + this[key] = val; + } else if (typeof key === 'object') { + Object.keys(key).forEach((item) => { + this[item] = key[item]; + }); + } + } + + batchOn(events, lisenter) { + if (!Array.isArray(events)) return; + events.forEach((event) => this.on(event, lisenter)); + } + + batchOnce(events, lisenter) { + if (!Array.isArray(events)) return; + events.forEach((event) => this.once(event, lisenter)); + } + + batchOff(events, lisenter) { + if (!Array.isArray(events)) return; + events.forEach((event) => this.off(event, lisenter)); + } +} diff --git a/packages/rax-render/src/utils/dataHelper.js b/packages/rax-render/src/utils/dataHelper.js new file mode 100644 index 000000000..6c838a739 --- /dev/null +++ b/packages/rax-render/src/utils/dataHelper.js @@ -0,0 +1,299 @@ +import { transformArrayToMap, isJSFunction, transformStringToFunction, clone } from './index'; +import { jsonp, mtop, request, get, post, bzb } from './request'; + +const DS_STATUS = { + INIT: 'init', + LOADING: 'loading', + LOADED: 'loaded', + ERROR: 'error' +}; + +export default class DataHelper { + constructor(comp, config = {}, appHelper, parser) { + this.host = comp; + this.config = config; + this.parser = parser; + this.ajaxList = (config && config.list) || []; + this.ajaxMap = transformArrayToMap(this.ajaxList, 'id'); + this.dataSourceMap = this.generateDataSourceMap(); + this.appHelper = appHelper; + } + + // 重置config,dataSourceMap状态会被重置; + resetConfig(config = {}) { + this.config = config; + this.ajaxList = (config && config.list) || []; + this.ajaxMap = transformArrayToMap(this.ajaxList, 'id'); + this.dataSourceMap = this.generateDataSourceMap(); + return this.dataSourceMap; + } + + // 更新config,只会更新配置,状态保存; + updateConfig(config = {}) { + this.config = config; + this.ajaxList = (config && config.list) || []; + const ajaxMap = transformArrayToMap(this.ajaxList, 'id'); + // 删除已经移除的接口 + Object.keys(this.ajaxMap).forEach(key => { + if (!ajaxMap[key]) { + delete this.dataSourceMap[key]; + } + }); + this.ajaxMap = ajaxMap; + // 添加未加入到dataSourceMap中的接口 + this.ajaxList.forEach(item => { + if (!this.dataSourceMap[item.id]) { + this.dataSourceMap[item.id] = { + status: DS_STATUS.INIT, + load: (...args) => { + return this.getDataSource(item.id, ...args); + } + }; + } + }); + return this.dataSourceMap; + } + + generateDataSourceMap() { + const res = {}; + this.ajaxList.forEach(item => { + res[item.id] = { + status: DS_STATUS.INIT, + load: (...args) => { + return this.getDataSource(item.id, ...args); + } + }; + }); + return res; + } + + updateDataSourceMap(id, data, error) { + this.dataSourceMap[id].error = error ? error : undefined; + this.dataSourceMap[id].data = data; + this.dataSourceMap[id].status = error ? DS_STATUS.ERROR : DS_STATUS.LOADED; + } + + getInitData() { + const initSyncData = this.parser(this.ajaxList).filter(item => { + if (item.isInit) { + this.dataSourceMap[item.id].status = DS_STATUS.LOADING; + return true; + } + return false; + }); + return this.asyncDataHandler(initSyncData).then(res => { + let dataHandler = this.config.dataHandler; + if (isJSFunction(dataHandler)) { + dataHandler = transformStringToFunction(dataHandler.value); + } + if (!dataHandler || typeof dataHandler !== 'function') return res; + try { + return dataHandler.call(this.host, res); + } catch (e) { + console.error('请求数据处理函数运行出错', e); + return; + } + }); + } + + getDataSource(id, params, otherOptions, callback) { + const req = this.parser(this.ajaxMap[id]); + const options = req.options || {}; + if (typeof otherOptions === 'function') { + callback = otherOptions; + otherOptions = {}; + } + const { headers, ...otherProps } = otherOptions || {}; + if (!req) { + console.warn(`getDataSource API named ${id} not exist`); + return; + } + return this.asyncDataHandler([ + { + ...req, + options: { + ...options, + // 支持参数为array的情况,当参数为array时,不做参数合并 + params: + Array.isArray(options.params) || Array.isArray(params) + ? params || options.params + : { + ...options.params, + ...params + }, + headers: { + ...options.headers, + ...headers + }, + ...otherProps + } + } + ]) + .then(res => { + try { + callback && callback(res && res[id]); + } catch (e) { + console.error('load请求回调函数报错', e); + } + + return res && res[id]; + }) + .catch(err => { + try { + callback && callback(null, err); + } catch (e) { + console.error('load请求回调函数报错', e); + } + + return err; + }); + } + + asyncDataHandler(asyncDataList) { + return new Promise((resolve, reject) => { + const allReq = []; + const doserReq = []; + const doserList = []; + const beforeRequest = this.appHelper && this.appHelper.utils && this.appHelper.utils.beforeRequest; + const afterRequest = this.appHelper && this.appHelper.utils && this.appHelper.utils.afterRequest; + const csrfInput = document.getElementById('_csrf_token'); + const _tb_token_ = csrfInput && csrfInput.value; + asyncDataList.map(req => { + const { id, type, options } = req; + if (!id || !type) return; + if (type === 'doServer') { + const { uri, params } = options || {}; + if (!uri) return; + doserList.push(id); + doserReq.push({ name: uri, package: 'cms', params }); + } else { + allReq.push(req); + } + }); + + if (doserReq.length > 0) { + allReq.push({ + type: 'doServer', + options: { + uri: '/nrsService.do', + cors: true, + method: 'POST', + params: { + data: JSON.stringify(doserReq), + _tb_token_ + } + } + }); + } + if (allReq.length === 0) resolve({}); + const res = {}; + Promise.all( + allReq.map(item => { + return new Promise(resolve => { + const { type, id, dataHandler, options } = item; + const doFetch = (type, options) => { + this.fetchOne(type, options) + .then(async data => { + if (afterRequest) { + this.appHelper.utils.afterRequest(item, data, undefined, async (data, error) => { + await fetchHandler(data, error); + }); + } else { + await fetchHandler(data, undefined); + } + }) + .catch(async err => { + if (afterRequest) { + // 必须要这么调用,否则beforeRequest中的this会丢失 + this.appHelper.utils.afterRequest(item, undefined, err, async (data, error) => { + await fetchHandler(data, error); + }); + } else { + await fetchHandler(undefined, err); + } + }); + }; + const fetchHandler = async (data, error) => { + if (type === 'doServer') { + if (!Array.isArray(data)) { + data = [data]; + } + doserList.forEach(async (id, idx) => { + const req = this.ajaxMap[id]; + if (req) { + res[id] = await this.dataHandler(id, req.dataHandler, data && data[idx], error); + this.updateDataSourceMap(id, res[id], error); + } + }); + } else { + res[id] = await this.dataHandler(id, dataHandler, data, error); + this.updateDataSourceMap(id, res[id], error); + } + resolve(); + }; + + if (type === 'doServer') { + doserList.forEach(item => { + this.dataSourceMap[item].status = DS_STATUS.LOADING; + }); + } else { + this.dataSourceMap[id].status = DS_STATUS.LOADING; + } + // 请求切片 + if (beforeRequest) { + // 必须要这么调用,否则beforeRequest中的this会丢失 + this.appHelper.utils.beforeRequest(item, clone(options), options => doFetch(type, options)); + } else { + doFetch(type, options); + } + }); + }) + ) + .then(() => { + resolve(res); + }) + .catch(e => { + reject(e); + }); + }); + } + + async dataHandler(id, dataHandler, data, error) { + if (isJSFunction(dataHandler)) { + dataHandler = transformStringToFunction(dataHandler.value); + } + if (!dataHandler || typeof dataHandler !== 'function') return data; + try { + return await dataHandler.call(this.host, data, error); + } catch (e) { + console.error('[' + id + ']单个请求数据处理函数运行出错', e); + return; + } + } + + fetchOne(type, options) { + let { uri, method = 'GET', headers, params, ...otherProps } = options; + otherProps = otherProps || {}; + switch (type) { + case 'mtop': + method && (otherProps.method = method); + return mtop(uri, params, otherProps); + case 'jsonp': + return jsonp(uri, params, otherProps); + case 'bzb': + return bzb(uri, params, { + method, + headers, + ...otherProps + }); + default: + method = method.toUpperCase(); + if (method === 'GET') { + return get(uri, params, headers, otherProps); + } else if (method === 'POST') { + return post(uri, params, headers, otherProps); + } + return request(uri, method, params, headers, otherProps); + } + } +} diff --git a/packages/rax-render/src/utils/index.js b/packages/rax-render/src/utils/index.js new file mode 100644 index 000000000..814f1edc9 --- /dev/null +++ b/packages/rax-render/src/utils/index.js @@ -0,0 +1,718 @@ +import Debug from 'debug'; +import _keymaster from 'keymaster'; +import { forEach as _forEach, shallowEqual as _shallowEqual } from '@ali/b3-one/lib/obj'; +import { serialize as serializeParams } from '@ali/b3-one/lib/url'; +import _moment from 'moment'; +import 'moment/locale/zh-cn'; +import _pick from 'lodash/pick'; +import _deepEqual from 'lodash/isEqualWith'; +import _clone from 'lodash/cloneDeep'; +import _isEmpty from 'lodash/isEmpty'; +import _throttle from 'lodash/throttle'; +import _debounce from 'lodash/debounce'; +import _serialize from 'serialize-javascript'; +import * as _jsonuri from 'jsonuri'; +import IntlMessageFormat from 'intl-messageformat'; +import pkg from '../../package.json'; + +window.sdkVersion = pkg.version; + +export const moment = _moment; +moment.locale('zh-cn'); +export const forEach = _forEach; +export const shallowEqual = _shallowEqual; +export const keymaster = _keymaster; +export const pick = _pick; +export const deepEqual = _deepEqual; +export const clone = _clone; +export const isEmpty = _isEmpty; +export const throttle = _throttle; +export const debounce = _debounce; +export const serialize = _serialize; +export const jsonuri = _jsonuri; +export { get, post, jsonp, mtop, request } from './request'; + +const ReactIs = require('react-is'); + +const ReactPropTypesSecret = require('prop-types/lib/ReactPropTypesSecret'); + +const factoryWithTypeCheckers = require('prop-types/factoryWithTypeCheckers'); + +const PropTypes2 = factoryWithTypeCheckers(ReactIs.isElement, true); + +const EXPRESSION_TYPE = { + JSEXPRESSION: 'JSExpression', + JSFUNCTION: 'JSFunction', + JSSLOT: 'JSSlot' +}; +const EXPRESSION_REG = /^\{\{(\{.*\}|.*?)\}\}$/; +const hasSymbol = typeof Symbol === 'function' && Symbol['for']; +const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol['for']('react.forward_ref') : 0xead0; +const debug = Debug('utils:index'); + +const ENV = { + TBE: 'TBE', + WEBIDE: 'WEB-IDE', + VSCODE: 'VSCODE', + WEB: 'WEB' +}; + +/** + * @name isSchema + * @description 判断是否是模型结构 + */ +export function isSchema(schema, ignoreArr) { + if (isEmpty(schema)) return false; + if (!ignoreArr && Array.isArray(schema)) return schema.every(item => isSchema(item)); + return !!(schema.componentName && schema.props && (typeof schema.props === 'object' || isJSExpression(schema.props))); +} + +export function isFileSchema(schema) { + if (isEmpty(schema)) return false; + return ['Page', 'Block', 'Component', 'Addon', 'Temp'].includes(schema.componentName); +} + +// 判断当前页面是否被嵌入到同域的页面中 +export function inSameDomain() { + try { + return window.parent !== window && window.parent.location.host === window.location.host; + } catch (e) { + return false; + } +} + +export function getFileCssName(fileName) { + if (!fileName) return; + let name = fileName.replace(/([A-Z])/g, '-$1').toLowerCase(); + return ('luna-' + name) + .split('-') + .filter(p => !!p) + .join('-'); +} + +export function isJSSlot(obj) { + return obj && typeof obj === 'object' && EXPRESSION_TYPE.JSSLOT === obj.type; +} +export function isJSFunction(obj) { + return obj && typeof obj === 'object' && EXPRESSION_TYPE.JSFUNCTION === obj.type; +} +export function isJSExpression(obj) { + //兼容两种写法,有js构造表达式的情况 + const isJSExpressionObj = + obj && typeof obj === 'object' && EXPRESSION_TYPE.JSEXPRESSION === obj.type && typeof obj.value === 'string'; + const isJSExpressionStr = typeof obj === 'string' && EXPRESSION_REG.test(obj.trim()); + return isJSExpressionObj || isJSExpressionStr; +} + +/** + * @name wait + * @description 等待函数 + */ +export function wait(ms) { + return new Promise(resolve => setTimeout(() => resolve(true), ms)); +} + +export function curry(Comp, hocs = []) { + return hocs.reverse().reduce((pre, cur) => { + return cur(pre); + }, Comp); +} + +/** + * 获取parent window上的值,需要做同域判断 + * @param {string} key + */ +export function getParentWinValue(key) { + if (inSameDomain()) { + return window.parent && window.parent[key]; + } +} + +export function getValue(obj, path, defaultValue) { + if (isEmpty(obj) || typeof obj !== 'object') return defaultValue; + const res = path.split('.').reduce((pre, cur) => { + return pre && pre[cur]; + }, obj); + if (res === undefined) return defaultValue; + return res; +} + +export function parseObj(schemaStr) { + if (typeof schemaStr !== 'string') return schemaStr; + //默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象 + try { + if (inSameDomain() && window.parent.__newFunc) { + return window.parent.__newFunc(`"use strict"; return ${schemaStr}`)(); + } + return new Function(`"use strict"; return ${schemaStr}`)(); + } catch (err) { + return undefined; + } +} + +export function parseSearch(search) { + if (!search || typeof search !== 'string') { + return {}; + } + const str = search.replace(/^\?/, ''); + const paramStr = str.split('&'); + const res = {}; + paramStr.forEach(item => { + const regRes = item.split('='); + if (regRes[0] && regRes[1]) { + res[regRes[0]] = decodeURIComponent(regRes[1]); + } + }); + return res; +} + +export function fastClone(obj) { + return parseObj(serialize(obj, { unsafe: true })); +} + +// 更新obj的内容但不改变obj的指针 +export function fillObj(receiver = {}, ...suppliers) { + Object.keys(receiver).forEach(item => { + delete receiver[item]; + }); + Object.assign(receiver, ...suppliers); + return receiver; +} + +// 中划线转驼峰 +export function toHump(name) { + return name.replace(/\-(\w)/g, function(all, letter) { + return letter.toUpperCase(); + }); +} +// 驼峰转中划线 +export function toLine(name) { + return name.replace(/([A-Z])/g, '-$1').toLowerCase(); +} + +// 获取当前环境 +export function getEnv() { + const userAgent = navigator.userAgent; + const isVscode = /Electron\//.test(userAgent); + if (isVscode) return ENV.VSCODE; + const isTheia = window.is_theia === true; + if (isTheia) return ENV.WEBIDE; + return ENV.WEB; +} + +/** + * 合并skeleton配置 + * @param {*} 骨架默认配置 + * @param {*} 用户自定义配置 + */ +export function comboSkeletonConfig(defaultConfig = {}, customConfig) { + const { skeleton, theme, addons, hooks, shortCuts, extensions, constants, utils, i18n } = customConfig || {}; + + if (skeleton && skeleton.handler && typeof skeleton.handler === 'function') { + return skeleton.handler({ + skeleton, + ...defaultConfig + }); + } + + const defaultShortCuts = transformArrayToMap(defaultConfig.shortCuts || [], 'keyboard'); + const customShortCuts = transformArrayToMap(shortCuts || [], 'keyboard'); + const localeList = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP']; + const i18nConfig = {}; + localeList.forEach(key => { + i18nConfig[key] = { + ...(defaultConfig.i18n && defaultConfig.i18n[key]), + ...(i18n && i18n[key]) + }; + }); + return { + skeleton, + theme: { + ...defaultConfig.theme, + ...theme + }, + addons: { + ...defaultConfig.addons, + ...addons + }, + hooks: [...(defaultConfig.hooks || []), ...(hooks || [])], + shortCuts: Object.values({ + ...defaultShortCuts, + ...customShortCuts + }), + extensions: { + ...defaultConfig.extensions, + ...extensions + }, + constants: { + ...defaultConfig.constants, + ...constants + }, + utils: [...(defaultConfig.utils || []), ...(utils || [])], + i18n: i18nConfig + }; +} + +/** + * 用于构造国际化字符串处理函数 + * @param {*} locale 国际化标识,例如 zh-CN、en-US + * @param {*} messages 国际化语言包 + */ +export function generateI18n(locale = 'zh-CN', messages = {}) { + return (key, values = {}) => { + if (!messages || !messages[key]) return ''; + const formater = new IntlMessageFormat(messages[key], locale); + return formater.format(values); + }; +} + +/** + * 判断当前组件是否能够设置ref + * @param {*} Comp 需要判断的组件 + */ +export function acceptsRef(Comp) { + return ( + (Comp.$$typeof && Comp.$$typeof === REACT_FORWARD_REF_TYPE) || (Comp.prototype && Comp.prototype.isReactComponent) + ); +} + +/** + * 黄金令箭埋点 + * @param {String} gmKey 为黄金令箭业务类型 + * @param {Object} params 参数 + * @param {String} logKey 属性串 + */ +export function goldlog(gmKey, params = {}, logKey = 'other') { + // vscode 黄金令箭API + const sendIDEMessage = window.sendIDEMessage || getParentWinValue('sendIDEMessage'); + const goKey = serializeParams({ + sdkVersion: pkg.version, + env: getEnv(), + ...params + }); + if (sendIDEMessage) { + sendIDEMessage({ + action: 'goldlog', + data: { + logKey: `/iceluna.core.${logKey}`, + gmKey, + goKey + } + }); + } + window.goldlog && window.goldlog.record(`/iceluna.core.${logKey}`, gmKey, goKey, 'POST'); +} + +// utils为编辑器打包生成的utils文件内容,utilsConfig为数据库存放的utils配置 +export function generateUtils(utils, utilsConfig) { + if (!Array.isArray(utilsConfig)) return { ...utils }; + const res = {}; + utilsConfig.forEach(item => { + if (!item.name || !item.type || !item.content) return; + if (item.type === 'function' && typeof item.content === 'function') { + res[item.name] = item.content; + } else if (item.type === 'npm' && utils[item.name]) { + res[item.name] = utils[item.name]; + } + }); + return res; +} +// 复制到粘贴板 +export function setClipboardData(str) { + return new Promise((resolve, reject) => { + if (typeof str !== 'string') reject('不支持拷贝'); + if (navigator.clipboard) { + navigator.clipboard + .writeText(str) + .then(() => { + resolve(); + }) + .catch(err => { + reject('复制失败,请重试!', err); + }); + } else { + const textArea = document.createElement('textarea'); + textArea.value = str; + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + try { + let successful = document.execCommand('copy'); + if (successful) { + document.body.removeChild(textArea); + resolve(); + } + } catch (err) { + document.body.removeChild(textArea); + reject('复制失败,请重试!', err); + } + } + }); +} +// 获取粘贴板数据 +export function getClipboardData() { + return new Promise((resolve, reject) => { + if (window.clipboardData) { + resolve(window.clipboardData.getData('text')); + } else if (navigator.clipboard) { + return navigator.clipboard + .readText() + .then(res => { + resolve(res); + }) + .catch(err => { + reject('粘贴板获取失败', err); + }); + } else { + reject('粘贴板获取失败'); + } + }); +} +// 将函数返回结果转成promise形式,如果函数有返回值则根据返回值的bool类型判断是reject还是resolve,若函数无返回值默认执行resolve +export function transformToPromise(input) { + if (input instanceof Promise) return input; + return new Promise((resolve, reject) => { + if (input || input === undefined) { + resolve(); + } else { + reject(); + } + }); +} + +export function moveArrayItem(arr, sourceIdx, distIdx, direction) { + if ( + !Array.isArray(arr) || + sourceIdx === distIdx || + sourceIdx < 0 || + sourceIdx >= arr.length || + distIdx < 0 || + distIdx >= arr.length + ) + return arr; + const item = arr[sourceIdx]; + if (direction === 'after') { + arr.splice(distIdx + 1, 0, item); + } else { + arr.splice(distIdx, 0, item); + } + if (sourceIdx < distIdx) { + arr.splice(sourceIdx, 1); + } else { + arr.splice(sourceIdx + 1, 1); + } + return arr; +} + +export function transformArrayToMap(arr, key, overwrite = true) { + if (isEmpty(arr) || !Array.isArray(arr)) return {}; + const res = {}; + arr.forEach(item => { + const curKey = item[key]; + if (item[key] === undefined) return; + if (res[curKey] && !overwrite) return; + res[curKey] = item; + }); + return res; +} + +export function checkPropTypes(value, name, rule, componentName) { + if (typeof rule === 'string') { + rule = new Function(`"use strict"; const PropTypes = arguments[0]; return ${rule}`)(PropTypes2); + } + if (!rule || typeof rule !== 'function') { + console.warn('checkPropTypes should have a function type rule argument'); + return true; + } + const err = rule( + { + [name]: value + }, + name, + componentName, + 'prop', + null, + ReactPropTypesSecret + ); + if (err) { + console.warn(err); + } + return !err; +} + +export function transformSchemaToPure(obj) { + const pureObj = obj => { + if (Array.isArray(obj)) { + return obj.map(item => pureObj(item)); + } else if (typeof obj === 'object') { + // 对于undefined及null直接返回 + if (!obj) return obj; + const res = {}; + forEach(obj, (val, key) => { + if (key.startsWith('__') && key !== '__ignoreParse') return; + res[key] = pureObj(val); + }); + return res; + } + return obj; + }; + return pureObj(obj); +} + +export function transformSchemaToStandard(obj) { + const standardObj = obj => { + if (Array.isArray(obj)) { + return obj.map(item => standardObj(item)); + } else if (typeof obj === 'object') { + // 对于undefined及null直接返回 + if (!obj) return obj; + const res = {}; + forEach(obj, (val, key) => { + if (key.startsWith('__') && key !== '__ignoreParse') return; + if (isSchema(val) && key !== 'children' && obj.type !== 'JSSlot') { + res[key] = { + type: 'JSSlot', + value: standardObj(val) + }; + // table特殊处理 + if (key === 'cell') { + res[key].params = ['value', 'index', 'record']; + } + } else { + res[key] = standardObj(val); + } + }); + return res; + } else if (typeof obj === 'function') { + return { + type: 'JSFunction', + value: obj.toString() + }; + } else if (typeof obj === 'string' && EXPRESSION_REG.test(obj.trim())) { + const regRes = obj.trim().match(EXPRESSION_REG); + return { + type: 'JSExpression', + value: (regRes && regRes[1]) || '' + }; + } + return obj; + }; + return standardObj(obj, false); +} + +export function transformStringToFunction(str) { + if (typeof str !== 'string') return str; + if (inSameDomain() && window.parent.__newFunc) { + return window.parent.__newFunc(`"use strict"; return ${str}`)(); + } else { + return new Function(`"use strict"; return ${str}`)(); + } +} + +export function addCssTag(id, content) { + let styleTag = document.getElementById(id); + if (styleTag) { + styleTag.innerHTML = content; + return; + } + styleTag = document.createElement('style'); + styleTag.id = id; + styleTag.class = 'luna-style'; + styleTag.innerHTML = content; + document.head.appendChild(styleTag); +} + +// 注册快捷 +export function registShortCuts(config, appHelper) { + const keyboardFilter = (keymaster.filter = event => { + const eTarget = event.target || event.srcElement; + const tagName = eTarget.tagName; + const isInput = !!(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA'); + const isContenteditable = target => { + while (target) { + if (target.contentEditable === 'true') return true; + target = target.parentNode; + } + return false; + }; + if (isInput || isContenteditable(eTarget)) { + if (event.metaKey === true && [70, 83].includes(event.keyCode)) event.preventDefault(); //禁止触发chrome原生的页面保存或查找 + return false; + } else { + return true; + } + }); + + const ideMessage = appHelper.utils && appHelper.utils.ideMessage; + + //复制 + if (!document.copyListener) { + document.copyListener = e => { + if (!keyboardFilter(e) || appHelper.isCopying) return; + const schema = appHelper.schemaHelper && appHelper.schemaHelper.schemaMap[appHelper.activeKey]; + if (!schema || !isSchema(schema)) return; + appHelper.isCopying = true; + const schemaStr = serialize(transformSchemaToPure(schema), { + unsafe: true + }); + setClipboardData(schemaStr) + .then(() => { + ideMessage && ideMessage('success', '当前内容已复制到剪贴板,请使用快捷键Command+v进行粘贴'); + appHelper.emit('schema.copy', schemaStr, schema); + appHelper.isCopying = false; + }) + .catch(errMsg => { + ideMessage && ideMessage('error', errMsg); + appHelper.isCopying = false; + }); + }; + document.addEventListener('copy', document.copyListener); + if (getParentWinValue('vscode')) { + keymaster('command+c', document.copyListener); + } + } + + //粘贴 + if (!document.pasteListener) { + const doPaste = (e, text) => { + if (!keyboardFilter(e) || appHelper.isPasting) return; + const schemaHelper = appHelper.schemaHelper; + let targetKey = appHelper.activeKey; + let direction = 'after'; + const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey; + if (!targetKey || topKey === targetKey) { + const schemaHelper = appHelper.schemaHelper; + const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey; + if (!topKey) return; + targetKey = topKey; + direction = 'in'; + } + appHelper.isPasting = true; + const schema = parseObj(text); + if (!isSchema(schema)) { + appHelper.emit('illegalSchema.paste', text); + // ideMessage && ideMessage('error', '当前内容不是模型结构,不能粘贴进来!'); + console.warn('paste schema illegal'); + appHelper.isPasting = false; + return; + } + appHelper.emit('material.add', { + schema, + targetKey, + direction + }); + appHelper.isPasting = false; + appHelper.emit('schema.paste', schema); + }; + document.pasteListener = e => { + const clipboardData = e.clipboardData || window.clipboardData; + const text = clipboardData && clipboardData.getData('text'); + doPaste(e, text); + }; + document.addEventListener('paste', document.pasteListener); + if (getParentWinValue('vscode')) { + keymaster('command+v', e => { + const sendIDEMessage = getParentWinValue('sendIDEMessage'); + sendIDEMessage && + sendIDEMessage({ + action: 'readClipboard' + }) + .then(text => { + doPaste(e, text); + }) + .catch(err => { + console.warn(err); + }); + }); + } + } + + (config || []).forEach(item => { + keymaster(item.keyboard, ev => { + ev.preventDefault(); + item.handler(ev, appHelper, keymaster); + }); + }); +} + +// 取消注册快捷 +export function unRegistShortCuts(config) { + (config || []).forEach(item => { + keymaster.unbind(item.keyboard); + }); + if (getParentWinValue('vscode')) { + keymaster.unbind('command+c'); + keymaster.unbind('command+v'); + } + if (document.copyListener) { + document.removeEventListener('copy', document.copyListener); + delete document.copyListener; + } + if (document.pasteListener) { + document.removeEventListener('paste', document.pasteListener); + delete document.pasteListener; + } +} + +export function parseData(schema, self) { + if (isJSExpression(schema)) { + return parseExpression(schema, self); + } else if (typeof schema === 'string') { + return schema.trim(); + } else if (Array.isArray(schema)) { + return schema.map(item => parseData(item, self)); + } else if (typeof schema === 'function') { + return schema.bind(self); + } else if (typeof schema === 'object') { + // 对于undefined及null直接返回 + if (!schema) return schema; + const res = {}; + forEach(schema, (val, key) => { + if (key.startsWith('__')) return; + res[key] = parseData(val, self); + }); + return res; + } + return schema; +} + +/*全匹配{{开头,}}结尾的变量表达式,或者对象类型JSExpression,且均不支持省略this */ +export function parseExpression(str, self) { + try { + const contextArr = ['"use strict";', 'var __self = arguments[0];']; + contextArr.push('return '); + let tarStr; + //向前兼容,支持标准协议新格式 + if (typeof str === 'string') { + const regRes = str.trim().match(EXPRESSION_REG); + tarStr = regRes[1]; + } else { + tarStr = (str.value || '').trim(); + } + tarStr = tarStr.replace(/this(\W|$)/g, (a, b) => `__self${b}`); + tarStr = contextArr.join('\n') + tarStr; + //默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象 + if (inSameDomain() && window.parent.__newFunc) { + return window.parent.__newFunc(tarStr)(self); + } + return new Function(tarStr)(self); + } catch (err) { + debug('parseExpression.error', err, str, self); + return undefined; + } +} + +/** + * 判断组件配置中是否有reactNode且type为function的props + * @param {*} componentInfo + */ +export function hasReactNodeFuncProps(componentInfo) { + const isReactNodeFuncProps = config => { + if (config.type === 'ReactNode') { + return config.props && config.props.type === 'function'; + } else if (config.type === 'Mixin') { + return config.props && config.props.reactNodeProps && config.props.reactNodeProps.type === 'function'; + } + }; + return componentInfo && (componentInfo.props || []).some(item => isReactNodeFuncProps(item)); +} diff --git a/packages/rax-render/src/utils/request.js b/packages/rax-render/src/utils/request.js new file mode 100644 index 000000000..f03bd4203 --- /dev/null +++ b/packages/rax-render/src/utils/request.js @@ -0,0 +1,171 @@ +import 'whatwg-fetch'; +import fetchMtop from '@ali/lib-mtop'; +import fetchJsonp from 'fetch-jsonp'; +import bzbRequest from '@ali/bzb-request'; +import { serialize, buildUrl, parseUrl } from '@ali/b3-one/lib/url'; + +export function get(dataAPI, params = {}, headers = {}, otherProps = {}) { + headers = { + Accept: 'application/json', + ...headers + }; + dataAPI = buildUrl(dataAPI, params); + return request(dataAPI, 'GET', null, headers, otherProps); +} + +export function post(dataAPI, params = {}, headers = {}, otherProps = {}) { + headers = { + Accept: 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + ...headers + }; + return request( + dataAPI, + 'POST', + headers['Content-Type'].indexOf('application/json') > -1 || Array.isArray(params) + ? JSON.stringify(params) + : serialize(params), + headers, + otherProps + ); +} + +export function request(dataAPI, method = 'GET', data, headers = {}, otherProps = {}) { + switch (method) { + case 'PUT': + case 'DELETE': + headers = { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...headers + }; + data = JSON.stringify(data || {}); + break; + } + return new Promise((resolve, reject) => { + if (otherProps.timeout) { + setTimeout(() => { + reject(new Error('timeout')); + }, otherProps.timeout); + } + fetch(dataAPI, { + method, + credentials: 'include', + headers, + body: data, + ...otherProps + }) + .then(response => { + switch (response.status) { + case 200: + case 201: + case 202: + return response.json(); + case 204: + if (method === 'DELETE') { + return { + success: true + }; + } else { + return { + __success: false, + code: response.status + }; + } + case 400: + case 401: + case 403: + case 404: + case 406: + case 410: + case 422: + case 500: + return response + .json() + .then(res => { + return { + __success: false, + code: response.status, + data: res + }; + }) + .catch(() => { + return { + __success: false, + code: response.status + }; + }); + } + return null; + }) + .then(json => { + if (json && json.__success !== false) { + resolve(json); + } else { + delete json.__success; + reject(json); + } + }) + .catch(err => { + reject(err); + }); + }); +} + +export function jsonp(dataAPI, params = {}, otherProps = {}) { + return new Promise((resolve, reject) => { + otherProps = { + timeout: 5000, + ...otherProps + }; + fetchJsonp(buildUrl(dataAPI, params), otherProps) + .then(response => response.json()) + .then(json => { + if (json) { + resolve(json); + } else { + reject(); + } + }) + .catch(err => { + reject(err); + }); + }); +} + +export function mtop(dataAPI, params, otherProps = {}) { + fetchMtop.config.subDomain = otherProps.subDomain || 'm'; + return fetchMtop.request({ + api: dataAPI, + v: '1.0', + data: params, + ecode: otherProps.ecode || 0, + type: otherProps.method || 'GET', + dataType: otherProps.dataType || 'jsonp', + AntiFlood: true, // 防刷 + timeout: otherProps.timeout || 20000 + }); +} + +export function bzb(apiCode, params, otherProps = {}) { + // 通过url参数设置小二工作台请求环境 + const getUrlEnv = () => { + try { + if (window.parent && window.parent.location.host === window.location.host) { + const urlInfo = parseUrl(window.parent && window.parent.location.href); + return urlInfo && urlInfo.params && urlInfo.params._env; + } + const urlInfo = parseUrl(window.location.href); + return urlInfo && urlInfo.params && urlInfo.params._env; + } catch (e) { + return null; + } + }; + + otherProps.method = otherProps.method || 'GET'; + otherProps.env = getUrlEnv() || otherProps.env || 'prod'; + return bzbRequest(apiCode, { + data: params, + ...otherProps + }); +} From aaf6359a8da2e5c882bd715a34a0b4b3f09ef3c0 Mon Sep 17 00:00:00 2001 From: "wuji.xwt" Date: Sun, 21 Jun 2020 17:53:20 +0800 Subject: [PATCH 19/19] chore: add demo --- packages/rax-render/demo/index.jsx | 35 +++++++++++++++++++ packages/rax-render/demo/miniapp/app.js | 1 + packages/rax-render/demo/miniapp/app.json | 6 ++++ .../rax-render/demo/miniapp/pages/index.acss | 0 .../rax-render/demo/miniapp/pages/index.axml | 1 + .../rax-render/demo/miniapp/pages/index.js | 4 +++ .../rax-render/demo/miniapp/pages/index.json | 6 ++++ .../rax-render/demo/wechat-miniprogram/app.js | 1 + .../demo/wechat-miniprogram/app.json | 6 ++++ .../demo/wechat-miniprogram/pages/index.js | 4 +++ .../demo/wechat-miniprogram/pages/index.json | 6 ++++ .../demo/wechat-miniprogram/pages/index.wxml | 1 + .../demo/wechat-miniprogram/pages/index.wxss | 0 packages/rax-render/package.json | 3 +- 14 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 packages/rax-render/demo/index.jsx create mode 100644 packages/rax-render/demo/miniapp/app.js create mode 100644 packages/rax-render/demo/miniapp/app.json create mode 100644 packages/rax-render/demo/miniapp/pages/index.acss create mode 100644 packages/rax-render/demo/miniapp/pages/index.axml create mode 100644 packages/rax-render/demo/miniapp/pages/index.js create mode 100644 packages/rax-render/demo/miniapp/pages/index.json create mode 100644 packages/rax-render/demo/wechat-miniprogram/app.js create mode 100644 packages/rax-render/demo/wechat-miniprogram/app.json create mode 100644 packages/rax-render/demo/wechat-miniprogram/pages/index.js create mode 100644 packages/rax-render/demo/wechat-miniprogram/pages/index.json create mode 100644 packages/rax-render/demo/wechat-miniprogram/pages/index.wxml create mode 100644 packages/rax-render/demo/wechat-miniprogram/pages/index.wxss diff --git a/packages/rax-render/demo/index.jsx b/packages/rax-render/demo/index.jsx new file mode 100644 index 000000000..cfcae2a20 --- /dev/null +++ b/packages/rax-render/demo/index.jsx @@ -0,0 +1,35 @@ +import { createElement, render } from 'rax'; +import DriverUniversal from 'driver-universal'; +import View from 'rax-view'; +import Text from 'rax-text'; +import { Engine } from '../src/index'; + +const components = { + View, + Text, +}; + +const schema = { + componentName: 'Page', + fileName: 'home', + props: {}, + children: [ + { + componentName: 'View', + props: {}, + children: [ + { + componentName: 'Text', + props: { + type: 'primary', + }, + children: ['Welcome to Your Rax App!'], + }, + ], + }, + ], +}; + +render(, document.getElementById('root'), { + driver: DriverUniversal, +}); diff --git a/packages/rax-render/demo/miniapp/app.js b/packages/rax-render/demo/miniapp/app.js new file mode 100644 index 000000000..348293551 --- /dev/null +++ b/packages/rax-render/demo/miniapp/app.js @@ -0,0 +1 @@ +App({}); diff --git a/packages/rax-render/demo/miniapp/app.json b/packages/rax-render/demo/miniapp/app.json new file mode 100644 index 000000000..94127c774 --- /dev/null +++ b/packages/rax-render/demo/miniapp/app.json @@ -0,0 +1,6 @@ +{ + "pages": ["pages/index"], + "window": { + "defaultTitle": "demo" + } +} diff --git a/packages/rax-render/demo/miniapp/pages/index.acss b/packages/rax-render/demo/miniapp/pages/index.acss new file mode 100644 index 000000000..e69de29bb diff --git a/packages/rax-render/demo/miniapp/pages/index.axml b/packages/rax-render/demo/miniapp/pages/index.axml new file mode 100644 index 000000000..41b536b4c --- /dev/null +++ b/packages/rax-render/demo/miniapp/pages/index.axml @@ -0,0 +1 @@ + diff --git a/packages/rax-render/demo/miniapp/pages/index.js b/packages/rax-render/demo/miniapp/pages/index.js new file mode 100644 index 000000000..40772a2e7 --- /dev/null +++ b/packages/rax-render/demo/miniapp/pages/index.js @@ -0,0 +1,4 @@ +Page({ + onLoad() {}, + onShow() {} +}); diff --git a/packages/rax-render/demo/miniapp/pages/index.json b/packages/rax-render/demo/miniapp/pages/index.json new file mode 100644 index 000000000..89b15c54c --- /dev/null +++ b/packages/rax-render/demo/miniapp/pages/index.json @@ -0,0 +1,6 @@ +{ + "defaultTitle": "Miniapp Rax Text demo", + "usingComponents": { + "my-component": "../components/Target/index" + } +} diff --git a/packages/rax-render/demo/wechat-miniprogram/app.js b/packages/rax-render/demo/wechat-miniprogram/app.js new file mode 100644 index 000000000..348293551 --- /dev/null +++ b/packages/rax-render/demo/wechat-miniprogram/app.js @@ -0,0 +1 @@ +App({}); diff --git a/packages/rax-render/demo/wechat-miniprogram/app.json b/packages/rax-render/demo/wechat-miniprogram/app.json new file mode 100644 index 000000000..be00ced60 --- /dev/null +++ b/packages/rax-render/demo/wechat-miniprogram/app.json @@ -0,0 +1,6 @@ +{ + "pages": ["pages/index"], + "window": { + "title": "demo" + } +} diff --git a/packages/rax-render/demo/wechat-miniprogram/pages/index.js b/packages/rax-render/demo/wechat-miniprogram/pages/index.js new file mode 100644 index 000000000..40772a2e7 --- /dev/null +++ b/packages/rax-render/demo/wechat-miniprogram/pages/index.js @@ -0,0 +1,4 @@ +Page({ + onLoad() {}, + onShow() {} +}); diff --git a/packages/rax-render/demo/wechat-miniprogram/pages/index.json b/packages/rax-render/demo/wechat-miniprogram/pages/index.json new file mode 100644 index 000000000..9448c84ea --- /dev/null +++ b/packages/rax-render/demo/wechat-miniprogram/pages/index.json @@ -0,0 +1,6 @@ +{ + "title": "Wechat MiniProgram Rax Text demo", + "usingComponents": { + "my-component": "../components/Target/index" + } +} diff --git a/packages/rax-render/demo/wechat-miniprogram/pages/index.wxml b/packages/rax-render/demo/wechat-miniprogram/pages/index.wxml new file mode 100644 index 000000000..41b536b4c --- /dev/null +++ b/packages/rax-render/demo/wechat-miniprogram/pages/index.wxml @@ -0,0 +1 @@ + diff --git a/packages/rax-render/demo/wechat-miniprogram/pages/index.wxss b/packages/rax-render/demo/wechat-miniprogram/pages/index.wxss new file mode 100644 index 000000000..e69de29bb diff --git a/packages/rax-render/package.json b/packages/rax-render/package.json index ade1a3756..986341bd5 100644 --- a/packages/rax-render/package.json +++ b/packages/rax-render/package.json @@ -54,7 +54,8 @@ }, "devDependencies": { "@alib/build-scripts": "^0.1.0", - "build-plugin-rax-component": "^0.1.4" + "build-plugin-rax-component": "^0.1.4", + "driver-universal": "^3.1.3" }, "publishConfig": { "registry": "https://registry.npm.alibaba-inc.com"