diff --git a/packages/demo/build.json b/packages/demo/build.json index 623ad7a36..02e5fccff 100644 --- a/packages/demo/build.json +++ b/packages/demo/build.json @@ -1,7 +1,8 @@ { "entry": { "index": "src/index.jsx", - "react-simulator-renderer": "../react-simulator-renderer/src/index.js" + "react-simulator-renderer": "../react-simulator-renderer/src/index.js", + "preview": "src/preview.js" }, "vendor": false, "devServer": { diff --git a/packages/demo/package.json b/packages/demo/package.json index 2efe9ba32..b35294f08 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -10,11 +10,14 @@ "@ali/lowcode-editor-skeleton": "^0.8.0", "@ali/lowcode-plugin-components-pane": "^0.8.0", "@ali/lowcode-plugin-designer": "^0.8.0", - "@ali/lowcode-plugin-logo": "^0.8.0", - "@ali/lowcode-plugin-save": "^0.8.0", + "@ali/lowcode-plugin-sample-logo": "^0.8.0", + "@ali/lowcode-plugin-sample-save": "^0.8.0", "@ali/lowcode-plugin-undo-redo": "^0.8.0", + "@ali/lowcode-runtime": "^0.8.0", + "@ali/lowcode-react-renderer": "^0.8.0", "@alife/theme-lowcode-dark": "^0.1.0", "@alife/theme-lowcode-light": "^0.1.0", + "@alifd/next": "^1.19.21", "react": "^16.8.1", "react-dom": "^16.8.1" }, diff --git a/packages/demo/public/preview.html b/packages/demo/public/preview.html new file mode 100644 index 000000000..34c8cf86c --- /dev/null +++ b/packages/demo/public/preview.html @@ -0,0 +1,20 @@ + + + + + + + LowCodeEngine DEMO + + + + + + + + + + + + + diff --git a/packages/demo/src/config/components.js b/packages/demo/src/designer/config/components.js similarity index 100% rename from packages/demo/src/config/components.js rename to packages/demo/src/designer/config/components.js diff --git a/packages/demo/src/config/constants.js b/packages/demo/src/designer/config/constants.js similarity index 100% rename from packages/demo/src/config/constants.js rename to packages/demo/src/designer/config/constants.js diff --git a/packages/demo/src/config/skeleton.js b/packages/demo/src/designer/config/skeleton.js similarity index 100% rename from packages/demo/src/config/skeleton.js rename to packages/demo/src/designer/config/skeleton.js diff --git a/packages/demo/src/config/theme.scss b/packages/demo/src/designer/config/theme.scss similarity index 100% rename from packages/demo/src/config/theme.scss rename to packages/demo/src/designer/config/theme.scss diff --git a/packages/demo/src/config/utils.js b/packages/demo/src/designer/config/utils.js similarity index 100% rename from packages/demo/src/config/utils.js rename to packages/demo/src/designer/config/utils.js diff --git a/packages/demo/src/index.jsx b/packages/demo/src/index.jsx index f666fe2c5..342bb86fb 100644 --- a/packages/demo/src/index.jsx +++ b/packages/demo/src/index.jsx @@ -2,12 +2,12 @@ import React from 'react'; import ReactDOM from 'react-dom'; import Skeleton from '@ali/lowcode-editor-skeleton'; import { registerSetters } from '@ali/lowcode-setters'; -import config from './config/skeleton'; -import components from './config/components'; -import utils from './config/utils'; +import config from './designer/config/skeleton'; +import components from './designer/config/components'; +import utils from './designer/config/utils'; import './global.scss'; -import './config/theme.scss'; +import './designer/config/theme.scss'; registerSetters(); diff --git a/packages/demo/src/preview.js b/packages/demo/src/preview.js index e69de29bb..3ed2c2eca 100644 --- a/packages/demo/src/preview.js +++ b/packages/demo/src/preview.js @@ -0,0 +1,22 @@ +import { run, Boot } from '@ali/lowcode-runtime'; +import Renderer from '@ali/lowcode-react-renderer'; +import FusionLoading from './preview/plugins/loading/fusion'; +import BasicLayout from './preview/layouts/BasicLayout'; +import provider from './preview/plugins/provider'; + +// 注册渲染模块 +Boot.registerRenderer(Renderer); + +// 注册布局组件,可注册多个 +Boot.registerLayout('BasicLayout', BasicLayout); + +// 注册页面 loading +Boot.registerLoading(FusionLoading); + +const appProvider = provider.create('lowcode_demo'); // 入参为应用唯一标识 + +// 异步加载应用配置 +appProvider.then(({ App, config }) => { + // 启动应用 + run(App, config); +}); diff --git a/packages/demo/src/preview/config/app.js b/packages/demo/src/preview/config/app.js new file mode 100644 index 000000000..c1ad5b57c --- /dev/null +++ b/packages/demo/src/preview/config/app.js @@ -0,0 +1,21 @@ +export default { + sdkVersion: '1.0.3', + historyMode: 'hash', // 浏览器路由:brower 哈希路由:hash + constainerId: 'app', + layout: { + componentName: 'BasicLayout', + props: { + name: '低代码引擎预览 demo', + logo: { + src: 'https://img.alicdn.com/tfs/TB1kAfWyrY1gK0jSZTEXXXDQVXa-75-33.png', + width: 40, + height: 20, + }, + }, + }, + theme: { + package: '@alife/theme-fusion', + version: '^0.1.0', + }, + compDependencies: [], +}; diff --git a/packages/demo/src/preview/config/components.js b/packages/demo/src/preview/config/components.js new file mode 100644 index 000000000..91aef7993 --- /dev/null +++ b/packages/demo/src/preview/config/components.js @@ -0,0 +1,12 @@ +/** + * 内置组件 + */ +import Engine from '@ali/iceluna-sdk/lib/engine'; +import Page from '@ali/iceluna-sdk/lib/engine/pageEngine' +import Div from '@ali/iceluna-comp-div'; + +export default { + Engine, + Page, + Div, +} diff --git a/packages/demo/src/preview/config/componentsMap.js b/packages/demo/src/preview/config/componentsMap.js new file mode 100644 index 000000000..a9bb73edb --- /dev/null +++ b/packages/demo/src/preview/config/componentsMap.js @@ -0,0 +1,53 @@ +export default { + Button: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Button', + }, + 'Button.Group': { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Button', + subName: 'Group', + }, + Input: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Input', + }, + Form: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Form', + }, + 'Form.Item': { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Form', + subName: 'Item', + }, + NumberPicker: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'NumberPicker', + }, + Select: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Select', + }, + 'Select.Option': { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Select', + subName: 'Option', + }, +}; diff --git a/packages/demo/src/preview/config/utils.js b/packages/demo/src/preview/config/utils.js new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/packages/demo/src/preview/config/utils.js @@ -0,0 +1 @@ +export default {}; diff --git a/packages/demo/src/preview/layouts/BasicLayout/index.js b/packages/demo/src/preview/layouts/BasicLayout/index.js new file mode 100644 index 000000000..6a51fdc75 --- /dev/null +++ b/packages/demo/src/preview/layouts/BasicLayout/index.js @@ -0,0 +1,27 @@ +import { Search, Icon, Shell } from '@alifd/next'; +import './index.less'; + +// eslint-disable-next-line react/prop-types +export default ({ name, children, logo }) => ( + + + logo + {name} + + + + + + + 用户头像 + MyName + + + {children} + + + Alibaba Fusion + @ 2019 Alibaba Piecework 版权所有 + + +); diff --git a/packages/demo/src/preview/layouts/BasicLayout/index.less b/packages/demo/src/preview/layouts/BasicLayout/index.less new file mode 100644 index 000000000..000abd1c8 --- /dev/null +++ b/packages/demo/src/preview/layouts/BasicLayout/index.less @@ -0,0 +1,24 @@ +@header-height: 52px; + +.avatar { + width: 24px; + height: 24px; + border-radius: 50%; + vertical-align: middle; +} + +.basic-shell { + min-height: 100vh; + .next-shell-header { + height: @header-height; + } + .next-shell-main { + flex: 1; + display: flex; + flex-flow: column; + min-height: calc(100% - @header-height); + .next-shell-sub-main { + flex: 1; + } + } +} diff --git a/packages/demo/src/preview/plugins/loading/deep/index.less b/packages/demo/src/preview/plugins/loading/deep/index.less new file mode 100644 index 000000000..2b59ea5ef --- /dev/null +++ b/packages/demo/src/preview/plugins/loading/deep/index.less @@ -0,0 +1,11 @@ +.recore-loading { + width: 48px; + height: 48px; + background: url(https://g.alicdn.com/uxcore/pic/loading.svg) center no-repeat; + background-size: contain; + position: fixed; + top: 50%; + left: 50%; + margin-top: -24px; + margin-left: -24px; +} diff --git a/packages/demo/src/preview/plugins/loading/deep/index.tsx b/packages/demo/src/preview/plugins/loading/deep/index.tsx new file mode 100644 index 000000000..02b2fc692 --- /dev/null +++ b/packages/demo/src/preview/plugins/loading/deep/index.tsx @@ -0,0 +1,3 @@ +import './index.less'; + +export default () =>
; diff --git a/packages/demo/src/preview/plugins/loading/fusion/index.less b/packages/demo/src/preview/plugins/loading/fusion/index.less new file mode 100644 index 000000000..0e53165cb --- /dev/null +++ b/packages/demo/src/preview/plugins/loading/fusion/index.less @@ -0,0 +1,9 @@ +.fusion-loading { + width: 48px; + height: 48px; + position: fixed; + top: 50%; + left: 50%; + margin-top: -24px; + margin-left: -24px; +} diff --git a/packages/demo/src/preview/plugins/loading/fusion/index.tsx b/packages/demo/src/preview/plugins/loading/fusion/index.tsx new file mode 100644 index 000000000..e61bfd9f0 --- /dev/null +++ b/packages/demo/src/preview/plugins/loading/fusion/index.tsx @@ -0,0 +1,4 @@ +import { Loading } from '@alifd/next'; +import './index.less'; + +export default () => ; diff --git a/packages/demo/src/preview/plugins/provider.ts b/packages/demo/src/preview/plugins/provider.ts new file mode 100644 index 000000000..d654af45e --- /dev/null +++ b/packages/demo/src/preview/plugins/provider.ts @@ -0,0 +1,117 @@ +import { createElement } from 'react'; +import { Provider, Boot, Router, navigator } from '@ali/lowcode-runtime'; +import appConfig from '../config/app'; +import builtInComps from '../config/components'; +import componentsMap from '../config/componentsMap'; +import util from '../config/utils'; +import { buildComponents } from './utils'; + +// 定制加载应用配置的逻辑 +class PreviewProvider extends Provider { + // 定制获取、处理应用配置(组件、插件、路由模式、布局等)的逻辑 + async getAppData(appkey: string, restOptions?: any): Promise { + const { historyMode, layout, constainerId } = appConfig; + const appSchemaStr: any = localStorage.getItem('lce-dev-store'); + const appSchema = JSON.parse(appSchemaStr || ''); + const history = { + mode: historyMode || 'hash', + basement: '/', + }; + this.layout = layout; + const routes: any = {}; + appSchema.componentsTree.forEach((page: any, idx: number) => { + if (!page.fileName) { + return; + } + const pageId = page.fileName; + routes[pageId] = `/${pageId}`; + }); + this.routerConfig = routes; + this.componentsMap = componentsMap; + this.globalComponents = { ...builtInComps, ...buildComponents({ '@alifd/next': 'Next' }, componentsMap) }; + this.globalUtils = util; + return { + history, + globalComponents: this.globalComponents, + globalUtils: this.globalUtils, + constainerId, + }; + } + + // 定制获取、处理页面 schema 的逻辑 + async getPageData(pageId: string, restOptions?: any) { + const appSchemaStr = localStorage.getItem('lce-dev-store'); + const appSchema = JSON.parse(appSchemaStr || ''); + const idx = appSchema.componentsTree.findIndex( + (page: any, idx: number) => (page.fileName || `page${idx}`) === pageId, + ); + const schema = appSchema.componentsTree[idx]; + return schema; + } + + // 定制构造根组件的逻辑,如切换路由机制 + createApp() { + if (!this.routerConfig) { + return; + } + const routes: Array<{ path: string; children: any; exact: boolean; keepAlive: boolean }> = []; + let homePageId = ''; + Object.keys(this.routerConfig).forEach((pageId: string, idx: number) => { + if (!pageId) { + return; + } + const path = this.routerConfig[pageId]; + if (idx === 0 || path === '/') { + homePageId = pageId; + } + routes.push({ + path, + children: (props: any) => this.getLazyComponent(pageId, props), + exact: true, + keepAlive: true, + }); + }); + if (homePageId) { + routes.push({ + path: '**', + children: (props: any) => this.getLazyComponent(homePageId, { ...props }), + exact: true, + keepAlive: true, + }); + } + const RouterView = (props: any) => { + return createElement(Router as any, { + routes, + components: this.globalComponents, + utils: this.globalUtils, + componentsMap: this.componentsMap, + ...props, + }); + }; + let App; + if (!this.layout || !(this.layout as any).componentName) { + App = (props: any) => createElement(RouterView, { ...props }); + return App; + } + const { componentName: layoutName, props: layoutProps } = this.layout as any; + const Layout = Boot.getLayout(layoutName); + if (Layout) { + App = (props: any) => + createElement( + Layout, + { + ...layoutProps, + onNavChange: ({ selectedKey }: any) => { + navigator.goto(`/${selectedKey}`); + }, + }, + RouterView({ props }), + ); + } else { + App = (props: any) => createElement(RouterView, props); + } + return App; + } +} + +export default new PreviewProvider(); diff --git a/packages/demo/src/preview/plugins/utils.js b/packages/demo/src/preview/plugins/utils.js new file mode 100644 index 000000000..b65d4864e --- /dev/null +++ b/packages/demo/src/preview/plugins/utils.js @@ -0,0 +1,74 @@ +function isESModule(obj) { + return obj && obj.__esModule; +} + +function getSubComponent(library, paths) { + const l = paths.length; + if (l < 1 || !library) { + return library; + } + let i = 0; + let component; + while (i < l) { + const key = paths[i]; + let ex; + try { + component = library[key]; + } catch (e) { + ex = e; + component = null; + } + if (i === 0 && component == null && key === 'default') { + if (ex) { + return l === 1 ? library : null; + } + component = library; + } else if (component == null) { + return null; + } + library = component; + i++; + } + return component; +} + +function accessLibrary(library) { + if (typeof library !== 'string') { + return library; + } + + return window[library]; +} + +function findComponent(libraryMap, componentName, npm) { + if (!npm) { + return accessLibrary(componentName); + } + // libraryName the key access to global + // export { exportName } from xxx exportName === global.libraryName.exportName + // export exportName from xxx exportName === global.libraryName.default || global.libraryName + // export { exportName as componentName } from package + // if exportName == null exportName === componentName; + // const componentName = exportName.subName, if exportName empty subName donot use + const exportName = npm.exportName || npm.componentName || componentName; + const libraryName = libraryMap[npm.package] || exportName; + const library = accessLibrary(libraryName); + const paths = npm.exportName && npm.subName ? npm.subName.split('.') : []; + if (npm.destructuring) { + paths.unshift(exportName); + } else if (isESModule(library)) { + paths.unshift('default'); + } + return getSubComponent(library, paths); +} + +export function buildComponents(libraryMap, componentsMap) { + const components = {}; + Object.keys(componentsMap).forEach((componentName) => { + const component = findComponent(libraryMap, componentName, componentsMap[componentName]); + if (component) { + components[componentName] = component; + } + }); + return components; +} diff --git a/packages/runtime/index.d.ts b/packages/runtime/index.d.ts index 5988aa400..6e3013290 100644 --- a/packages/runtime/index.d.ts +++ b/packages/runtime/index.d.ts @@ -1,25 +1,19 @@ import { ReactType } from 'react'; +type HistoryMode = 'browser' | 'hash'; -export as namespace LowCodeEngineRuntime; -export = LowCodeEngineRuntime; +interface ComponentsMap { + [key: string]: ReactType; +} -declare module LowCodeEngineRuntime { - type HistoryMode = 'browser' | 'hash'; +interface UtilsMap { + [key: string]: any; +} - interface ComponentsMap { - [key: string]: ReactType; - } +export interface AppConfig { + history?: HistoryMode; + globalComponents?: ComponentsMap; + globalUtils?: UtilsMap; + containerId?: string; +} - interface UtilsMap { - [key: string]: any; - } - - interface AppConfig { - history?: HistoryMode; - globalComponents?: ComponentsMap; - globalUtils?: UtilsMap; - containerId?: string; - } - - function runApp(Component: any, config?: AppConfig | (() => AppConfig), exposeModule?: boolean): any; -} \ No newline at end of file +export function run(Component: any, config?: AppConfig | (() => AppConfig)): any;