ci: add preview demo

This commit is contained in:
kangwei 2020-03-30 14:32:18 +08:00
commit adb35b8c21
33 changed files with 446 additions and 48 deletions

View File

@ -1,7 +1,8 @@
{
"entry": {
"index": "src/index.jsx",
"react-simulator-renderer": "../react-simulator-renderer/src/index.js"
"index": "src/index.ts",
"react-simulator-renderer": "../react-simulator-renderer/src/index.ts",
"preview": "src/preview.ts"
},
"vendor": false,
"devServer": {

View File

@ -1,6 +1,7 @@
{
"entry": {
"lowcode-demo": "src/index.jsx"
"lowcode-editor": "src/index.ts",
"lowcode-preview": "src/preview.ts"
},
"vendor": false,
"externals": {

View File

@ -18,8 +18,11 @@
"@ali/lowcode-plugin-zh-en": "^0.8.0",
"@ali/lowcode-plugin-sample-logo": "^0.8.0",
"@ali/lowcode-plugin-sample-preview": "^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"
},

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1" />
<meta name="viewport" content="width=device-width" />
<title>LowCodeEngine DEMO</title>
<script src="https://g.alicdn.com/code/lib/react/16.9.0/umd/react.development.js"></script>
<script src="https://g.alicdn.com/code/lib/react-dom/16.9.0/umd/react-dom.development.js"></script>
<script src="https://g.alicdn.com/code/lib/prop-types/15.7.2/prop-types.js"></script>
<script> React.PropTypes = PropTypes; </script>
<script src="https://g.alicdn.com/mylib/moment/2.24.0/min/moment.min.js"></script>
<link rel="stylesheet" href="https://alifd.alicdn.com/npm/@alifd/next/1.11.6/next.min.css">
<script src="https://unpkg.alibaba-inc.com/@alifd/next@1.18.17/dist/next.min.js"></script>
</head>
<body>
</body>
</html>

View File

@ -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: [],
};

View File

@ -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,
}

View File

@ -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',
},
};

View File

@ -0,0 +1,22 @@
import { boot, run } from '@ali/lowcode-runtime';
import Renderer from '@ali/lowcode-react-renderer';
import FusionLoading from './plugins/loading/fusion';
import BasicLayout from './layouts/BasicLayout';
import provider from './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);
});

View File

@ -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 }) => (
<Shell className="basic-shell" type="dark" style={{ border: '1px solid #eee' }}>
<Shell.Branding>
<img src={logo.src} width={logo.width} height={logo.height} alt="logo" />
<span style={{ marginLeft: 10 }}>{name}</span>
</Shell.Branding>
<Shell.Navigation direction="hoz">
<Search key="2" shape="simple" type="dark" palceholder="Search" style={{ width: '200px' }} />
</Shell.Navigation>
<Shell.Action>
<Icon type="ic_tongzhi" />
<img src="https://img.alicdn.com/tfs/TB1.ZBecq67gK0jSZFHXXa9jVXa-904-826.png" className="avatar" alt="用户头像" />
<span style={{ marginLeft: 10 }}>MyName</span>
</Shell.Action>
<Shell.Content className="content">{children}</Shell.Content>
<Shell.Footer>
<span>Alibaba Fusion</span>
<span>@ 2019 Alibaba Piecework 版权所有</span>
</Shell.Footer>
</Shell>
);

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -0,0 +1,3 @@
import './index.less';
export default () => <div className="recore-loading" />;

View File

@ -0,0 +1,9 @@
.fusion-loading {
width: 48px;
height: 48px;
position: fixed;
top: 50%;
left: 50%;
margin-top: -24px;
margin-left: -24px;
}

View File

@ -0,0 +1,4 @@
import { Loading } from '@alifd/next';
import './index.less';
export default () => <Loading tip="加载中..." className="fusion-loading" />;

View File

@ -0,0 +1,114 @@
import { createElement } from 'react';
import { Provider, boot, Router } 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<any> {
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,
},
RouterView({ props }),
);
} else {
App = (props: any) => createElement(RouterView, props);
}
return App;
}
}
export default new PreviewProvider();

View File

@ -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;
}

View File

@ -0,0 +1 @@
export default {};

View File

@ -13,6 +13,7 @@ registerSetters();
const LCE_CONTAINER = document.getElementById('lce-container');
console.info('aeafeawef')
if (!LCE_CONTAINER) {
throw new Error('当前页面不存在 <div id="lce-container"></div> 节点.');
}

View File

@ -0,0 +1 @@
import "./editor"

View File

@ -0,0 +1 @@
import "./app";

View File

@ -1,7 +1,7 @@
import renderer from './renderer';
if (typeof window !== 'undefined') {
window.SimulatorRenderer = renderer;
(window as any).SimulatorRenderer = renderer;
}
export default renderer;

View File

@ -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;
}
export function run(Component: any, config?: AppConfig | (() => AppConfig)): any;

View File

@ -7,36 +7,36 @@ class Trunk {
private layouts: { [key: string]: TComponent } = {};
private loading: TComponent | null = null;
public registerRenderer(renderer: TComponent): any {
registerRenderer(renderer: TComponent): any {
this.renderer = renderer;
}
public registerLayout(componentName: string, Layout: TComponent): any {
registerLayout(componentName: string, Layout: TComponent): any {
if (!componentName || !Layout) {
return;
}
this.layouts[componentName] = Layout;
}
public registerLoading(component: TComponent) {
registerLoading(component: TComponent) {
if (!component) {
return;
}
this.loading = component;
}
public getLayout(componentName: string) {
getLayout(componentName: string) {
if (!componentName) {
return;
}
return this.layouts[componentName];
}
public getRenderer(): TComponent | null {
getRenderer(): TComponent | null {
return this.renderer;
}
public getLoading(): TComponent | null {
getLoading(): TComponent | null {
return this.loading;
}
}

View File

@ -1,5 +1,5 @@
import { navigator, Router, runApp as run } from '@ali/recore';
import Boot from './boot';
import boot from './boot';
import Provider from './provider';
export { run, Router, Boot, Provider, navigator };
export { run, Router, boot, Provider, navigator };

View File

@ -1,5 +1,5 @@
import { Component, createElement } from 'react';
import Boot from './boot';
import boot from './boot';
interface IProps {
getPageData: () => any;
@ -18,7 +18,7 @@ export default class LazyComponent extends Component<IProps, IState> {
};
}
public async componentDidMount() {
async componentDidMount() {
const { getPageData } = this.props;
if (getPageData && !this.state.schema) {
const schema = await getPageData();
@ -26,11 +26,11 @@ export default class LazyComponent extends Component<IProps, IState> {
}
}
public render() {
render() {
const { getPageData, ...restProps } = this.props;
const { schema } = this.state;
const Renderer = Boot.getRenderer();
const Loading = Boot.getLoading();
const Renderer = boot.getRenderer();
const Loading = boot.getLoading();
if (!Renderer || !schema) {
if (!Loading) {
return null;

View File

@ -23,18 +23,19 @@ export interface IAppConfig {
containerId?: string;
}
export default abstract class Provider {
public globalComponents: any = {};
public globalUtils: any = {};
public routerConfig: { [key: string]: string } = {};
public layout: { componentName: string; props: any } | null = null;
export default class Provider {
globalComponents: any = {};
globalUtils: any = {};
routerConfig: { [key: string]: string } = {};
layout: { componentName: string; props: any } | null = null;
componentsMap: any = null;
private lazyElementsMap: { [key: string]: any } = {};
constructor() {
this.init();
}
public create(appkey: string): Promise<IAppData> {
create(appkey: string): Promise<IAppData> {
return new Promise(async (resolve, reject) => {
try {
const config = await this.getAppData(appkey);
@ -49,21 +50,21 @@ export default abstract class Provider {
});
}
public async init() {
async init() {
console.log('init');
}
public async getAppData(appkey: string, restOptions?: any): Promise<object> {
async getAppData(appkey: string, restOptions?: any): Promise<object> {
console.log('getAppData');
return {};
}
public async getPageData(pageId: string, restOptions?: any): Promise<any> {
async getPageData(pageId: string, restOptions?: any): Promise<any> {
console.log('getPageData');
return;
}
public getLazyComponent(pageId: string, props: any): ReactElement | null {
getLazyComponent(pageId: string, props: any): ReactElement | null {
if (!pageId) {
return null;
}
@ -82,7 +83,7 @@ export default abstract class Provider {
}
}
public createApp() {
createApp() {
console.log('createApp');
return;
}