mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-03-07 02:47:12 +00:00
Merge branch 'demo' of gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine into v/0.8.0
This commit is contained in:
commit
0c7a8b0a7f
@ -16,5 +16,6 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div id="lce-container"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
export default {
|
export default {
|
||||||
sdkVersion: '1.0.3',
|
sdkVersion: '1.0.3',
|
||||||
historyMode: 'hash', // 浏览器路由:brower 哈希路由:hash
|
history: 'hash', // 浏览器路由:brower 哈希路由:hash
|
||||||
constainerId: 'app',
|
containerId: 'lce-container',
|
||||||
layout: {
|
layout: {
|
||||||
componentName: 'BasicLayout',
|
componentName: 'BasicLayout',
|
||||||
props: {
|
props: {
|
||||||
name: '低代码引擎预览 demo',
|
name: '低代码引擎预览 demo',
|
||||||
logo: {
|
logo: {
|
||||||
src: 'https://img.alicdn.com/tfs/TB1kAfWyrY1gK0jSZTEXXXDQVXa-75-33.png',
|
src: 'https://img.alicdn.com/tfs/TB1L.1QAeL2gK0jSZFmXXc7iXXa-90-90.png',
|
||||||
width: 40,
|
width: 25,
|
||||||
height: 20,
|
height: 25,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1,12 +0,0 @@
|
|||||||
/**
|
|
||||||
* 内置组件
|
|
||||||
*/
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
5
packages/demo/src/app/config/components.ts
Normal file
5
packages/demo/src/app/config/components.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* 内置组件
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {};
|
||||||
1
packages/demo/src/app/config/utils.ts
Normal file
1
packages/demo/src/app/config/utils.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default {};
|
||||||
@ -1,22 +1,20 @@
|
|||||||
import { boot, run } from '@ali/lowcode-runtime';
|
import { contribution, run } from '@ali/lowcode-runtime';
|
||||||
import Renderer from '@ali/lowcode-react-renderer';
|
import Renderer from '@ali/lowcode-react-renderer';
|
||||||
import FusionLoading from './plugins/loading/fusion';
|
import FusionLoading from './plugins/loading/fusion';
|
||||||
import BasicLayout from './layouts/BasicLayout';
|
import BasicLayout from './layouts/BasicLayout';
|
||||||
import provider from './plugins/provider';
|
import Preview from './plugins/provider';
|
||||||
|
|
||||||
// 注册渲染模块
|
// 注册渲染模块
|
||||||
boot.registerRenderer(Renderer);
|
contribution.registerRenderer(Renderer);
|
||||||
|
|
||||||
// 注册布局组件,可注册多个
|
// 注册布局组件,可注册多个
|
||||||
boot.registerLayout('BasicLayout', BasicLayout);
|
contribution.registerLayout('BasicLayout', BasicLayout);
|
||||||
|
|
||||||
// 注册页面 Loading
|
// 注册页面 Loading
|
||||||
boot.registerLoading(FusionLoading);
|
contribution.registerLoading(FusionLoading);
|
||||||
|
|
||||||
const appProvider = provider.create('lowcode_demo'); // 入参为应用唯一标识
|
// appKey:应用唯一标识
|
||||||
|
contribution.registerProvider(new Preview({ appKey: 'lowcode_demo' }));
|
||||||
|
|
||||||
// 异步加载应用配置
|
// 启动应用
|
||||||
appProvider.then(({ App, config }) => {
|
run();
|
||||||
// 启动应用
|
|
||||||
run(App, config);
|
|
||||||
});
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@header-height: 52px;
|
$header-height: 52px;
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
@ -10,13 +10,13 @@
|
|||||||
.basic-shell {
|
.basic-shell {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
.next-shell-header {
|
.next-shell-header {
|
||||||
height: @header-height;
|
height: $header-height;
|
||||||
}
|
}
|
||||||
.next-shell-main {
|
.next-shell-main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
min-height: calc(100% - @header-height);
|
min-height: calc(100% - $header-height);
|
||||||
.next-shell-sub-main {
|
.next-shell-sub-main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
@ -1,15 +1,22 @@
|
|||||||
import { Search, Icon, Shell } from '@alifd/next';
|
import { Search, Icon, Shell } from '@alifd/next';
|
||||||
import './index.less';
|
import './index.scss';
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
export default ({
|
||||||
export default ({ name, children, logo }) => (
|
name,
|
||||||
<Shell className="basic-shell" type="dark" style={{ border: '1px solid #eee' }}>
|
children,
|
||||||
|
logo,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
children: any;
|
||||||
|
logo: { src: string; width: number; height: number };
|
||||||
|
}) => (
|
||||||
|
<Shell className="basic-shell" style={{ border: '1px solid #eee' }}>
|
||||||
<Shell.Branding>
|
<Shell.Branding>
|
||||||
<img src={logo.src} width={logo.width} height={logo.height} alt="logo" />
|
<img src={logo.src} width={logo.width} height={logo.height} alt="logo" />
|
||||||
<span style={{ marginLeft: 10 }}>{name}</span>
|
<span style={{ marginLeft: 10 }}>{name}</span>
|
||||||
</Shell.Branding>
|
</Shell.Branding>
|
||||||
<Shell.Navigation direction="hoz">
|
<Shell.Navigation direction="hoz">
|
||||||
<Search key="2" shape="simple" type="dark" palceholder="Search" style={{ width: '200px' }} />
|
<Search key="2" shape="simple" type="dark" style={{ width: '200px' }} />
|
||||||
</Shell.Navigation>
|
</Shell.Navigation>
|
||||||
<Shell.Action>
|
<Shell.Action>
|
||||||
<Icon type="ic_tongzhi" />
|
<Icon type="ic_tongzhi" />
|
||||||
@ -1,11 +0,0 @@
|
|||||||
.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;
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import './index.less';
|
|
||||||
|
|
||||||
export default () => <div className="recore-loading" />;
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { Loading } from '@alifd/next';
|
import { Loading } from '@alifd/next';
|
||||||
import './index.less';
|
import './index.scss';
|
||||||
|
|
||||||
export default () => <Loading tip="加载中..." className="fusion-loading" />;
|
export default () => <Loading tip="加载中..." className="fusion-loading" />;
|
||||||
|
|||||||
@ -1,45 +1,45 @@
|
|||||||
import { createElement } from 'react';
|
import { ReactProvider, Utils } from '@ali/lowcode-runtime';
|
||||||
import { Provider, boot, Router } from '@ali/lowcode-runtime';
|
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
import builtInComps from '../config/components';
|
import builtInComps from '../config/components';
|
||||||
import componentsMap from '../config/componentsMap';
|
import componentsMap from '../config/componentsMap';
|
||||||
import util from '../config/utils';
|
import constants from '../config/constants';
|
||||||
import { buildComponents } from './utils';
|
import utils from '../config/utils';
|
||||||
|
|
||||||
// 定制加载应用配置的逻辑
|
// 定制加载应用配置的逻辑
|
||||||
class PreviewProvider extends Provider {
|
export default class Preview extends ReactProvider {
|
||||||
// 定制获取、处理应用配置(组件、插件、路由模式、布局等)的逻辑
|
// 定制获取、处理应用配置(组件、插件、路由模式、布局等)的逻辑
|
||||||
async getAppData(appkey: string, restOptions?: any): Promise<any> {
|
async getAppData(appkey: string): Promise<any> {
|
||||||
const { historyMode, layout, constainerId } = appConfig;
|
const { history, layout, containerId } = appConfig;
|
||||||
const appSchemaStr: any = localStorage.getItem('lce-dev-store');
|
const appSchemaStr: any = localStorage.getItem('lce-dev-store');
|
||||||
const appSchema = JSON.parse(appSchemaStr || '');
|
if (!appSchemaStr) {
|
||||||
const history = {
|
return;
|
||||||
mode: historyMode || 'hash',
|
}
|
||||||
basement: '/',
|
const appSchema = JSON.parse(appSchemaStr);
|
||||||
};
|
if (!appSchema) {
|
||||||
this.layout = layout;
|
return;
|
||||||
|
}
|
||||||
const routes: any = {};
|
const routes: any = {};
|
||||||
appSchema.componentsTree.forEach((page: any, idx: number) => {
|
appSchema.componentsTree.forEach((page: any) => {
|
||||||
if (!page.fileName) {
|
if (!page.fileName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const pageId = page.fileName;
|
const pageId = page.fileName;
|
||||||
routes[pageId] = `/${pageId}`;
|
routes[pageId] = `/${pageId}`;
|
||||||
});
|
});
|
||||||
this.routerConfig = routes;
|
|
||||||
this.componentsMap = componentsMap;
|
|
||||||
this.globalComponents = { ...builtInComps, ...buildComponents({ '@alifd/next': 'Next' }, componentsMap) };
|
|
||||||
this.globalUtils = util;
|
|
||||||
return {
|
return {
|
||||||
history,
|
history,
|
||||||
globalComponents: this.globalComponents,
|
layout,
|
||||||
globalUtils: this.globalUtils,
|
routes,
|
||||||
constainerId,
|
containerId,
|
||||||
|
components: { ...builtInComps, ...Utils.buildComponents({ '@alifd/next': 'Next' }, componentsMap) },
|
||||||
|
componentsMap,
|
||||||
|
utils: utils,
|
||||||
|
constants,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定制获取、处理页面 schema 的逻辑
|
// 定制获取、处理页面 schema 的逻辑
|
||||||
async getPageData(pageId: string, restOptions?: any) {
|
async getPageData(pageId: string) {
|
||||||
const appSchemaStr = localStorage.getItem('lce-dev-store');
|
const appSchemaStr = localStorage.getItem('lce-dev-store');
|
||||||
const appSchema = JSON.parse(appSchemaStr || '');
|
const appSchema = JSON.parse(appSchemaStr || '');
|
||||||
const idx = appSchema.componentsTree.findIndex(
|
const idx = appSchema.componentsTree.findIndex(
|
||||||
@ -48,67 +48,4 @@ class PreviewProvider extends Provider {
|
|||||||
const schema = appSchema.componentsTree[idx];
|
const schema = appSchema.componentsTree[idx];
|
||||||
return schema;
|
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();
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
import "./editor"
|
import './editor';
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
import "./app";
|
import './app';
|
||||||
|
|||||||
@ -25,7 +25,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
|||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* remove abstract identifer ([2e45266](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2e45266))
|
* remove abstract identifier ([2e45266](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2e45266))
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
@ -41,17 +41,9 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
|||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
<<<<<<< HEAD
|
* remove abstract identifier ([2e45266](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2e4526667d563dccb85b9e3c60d862500f308915))
|
||||||
* remove abstract identifer ([2e45266](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2e45266))
|
|
||||||
=======
|
|
||||||
* remove abstract identifer ([2e45266](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2e4526667d563dccb85b9e3c60d862500f308915))
|
|
||||||
>>>>>>> df955e1db90ff104cd11160def80113cfd6faccc
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
* complet preview ([56c16ff](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/56c16ff))
|
|
||||||
=======
|
|
||||||
* complet preview ([56c16ff](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/56c16ffa5c39c2d01abd9cfa90fea49a4539da1d))
|
* complet preview ([56c16ff](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/56c16ffa5c39c2d01abd9cfa90fea49a4539da1d))
|
||||||
>>>>>>> df955e1db90ff104cd11160def80113cfd6faccc
|
|
||||||
|
|||||||
@ -1,44 +0,0 @@
|
|||||||
import { ComponentClass, FunctionComponent } from 'react';
|
|
||||||
|
|
||||||
type TComponent = ComponentClass | FunctionComponent;
|
|
||||||
|
|
||||||
class Trunk {
|
|
||||||
private renderer: TComponent | null = null;
|
|
||||||
private layouts: { [key: string]: TComponent } = {};
|
|
||||||
private loading: TComponent | null = null;
|
|
||||||
|
|
||||||
registerRenderer(renderer: TComponent): any {
|
|
||||||
this.renderer = renderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
registerLayout(componentName: string, Layout: TComponent): any {
|
|
||||||
if (!componentName || !Layout) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.layouts[componentName] = Layout;
|
|
||||||
}
|
|
||||||
|
|
||||||
registerLoading(component: TComponent) {
|
|
||||||
if (!component) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.loading = component;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLayout(componentName: string) {
|
|
||||||
if (!componentName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return this.layouts[componentName];
|
|
||||||
}
|
|
||||||
|
|
||||||
getRenderer(): TComponent | null {
|
|
||||||
return this.renderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLoading(): TComponent | null {
|
|
||||||
return this.loading;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default new Trunk();
|
|
||||||
52
packages/runtime/src/core/contribution.ts
Normal file
52
packages/runtime/src/core/contribution.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { ReactType } from 'react';
|
||||||
|
import { IProvider } from './provider/base';
|
||||||
|
|
||||||
|
class Contribution {
|
||||||
|
private renderer: ReactType | null = null;
|
||||||
|
private layouts: { [key: string]: ReactType } = {};
|
||||||
|
private loading: ReactType | null = null;
|
||||||
|
private provider: any;
|
||||||
|
|
||||||
|
registerRenderer(renderer: ReactType): any {
|
||||||
|
this.renderer = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerLayout(componentName: string, Layout: ReactType): any {
|
||||||
|
if (!componentName || !Layout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.layouts[componentName] = Layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerLoading(component: ReactType) {
|
||||||
|
if (!component) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loading = component;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerProvider(provider: IProvider) {
|
||||||
|
this.provider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLayout(componentName: string) {
|
||||||
|
if (!componentName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return this.layouts[componentName];
|
||||||
|
}
|
||||||
|
|
||||||
|
getRenderer(): ReactType | null {
|
||||||
|
return this.renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLoading(): ReactType | null {
|
||||||
|
return this.loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
getProvider() {
|
||||||
|
return this.provider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new Contribution();
|
||||||
297
packages/runtime/src/core/provider/base.ts
Normal file
297
packages/runtime/src/core/provider/base.ts
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
import { IAppConfig, IUtils, IComponents, HistoryMode } from '../run';
|
||||||
|
|
||||||
|
interface IConstants {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IComponentMap {
|
||||||
|
componentName: string;
|
||||||
|
package?: string;
|
||||||
|
version?: string;
|
||||||
|
destructuring?: boolean;
|
||||||
|
exportName?: string;
|
||||||
|
subName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ILayoutConfig {
|
||||||
|
componentName: string;
|
||||||
|
props: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IRouterConfig {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IHistoryConfig {
|
||||||
|
mode: HistoryMode;
|
||||||
|
basement?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAppData {
|
||||||
|
history?: HistoryMode;
|
||||||
|
layout?: ILayoutConfig;
|
||||||
|
routes?: IRouterConfig;
|
||||||
|
containerId?: string;
|
||||||
|
components?: IComponents;
|
||||||
|
componentsMap?: IComponentMap[];
|
||||||
|
utils?: IUtils;
|
||||||
|
constants?: IConstants;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IOptions {
|
||||||
|
appKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComponentProps {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JSExpression {
|
||||||
|
type: string;
|
||||||
|
value: string;
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DataSourceItem {
|
||||||
|
id: string;
|
||||||
|
isInit: boolean;
|
||||||
|
type: string;
|
||||||
|
options: {
|
||||||
|
uri: string;
|
||||||
|
params: object;
|
||||||
|
method: string;
|
||||||
|
shouldFetch?: string;
|
||||||
|
willFetch?: string;
|
||||||
|
fit?: string;
|
||||||
|
didFetch?: string;
|
||||||
|
};
|
||||||
|
dataHandler: JSExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DataSource {
|
||||||
|
list: DataSourceItem[];
|
||||||
|
dataHandler: JSExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LifeCycles {
|
||||||
|
[key: string]: JSExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Methods {
|
||||||
|
[key: string]: JSExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComponentModel {
|
||||||
|
id?: string;
|
||||||
|
componentName: string;
|
||||||
|
fileName?: string;
|
||||||
|
props?: ComponentProps;
|
||||||
|
css?: string;
|
||||||
|
dataSource?: DataSource;
|
||||||
|
lifeCycles?: LifeCycles;
|
||||||
|
methods?: Methods;
|
||||||
|
children?: ComponentModel[];
|
||||||
|
condition?: JSExpression | boolean;
|
||||||
|
loop?: string[];
|
||||||
|
loopArgs?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IProvider {
|
||||||
|
init?(): void;
|
||||||
|
getAppData?(appkey: string): Promise<IAppData | undefined>;
|
||||||
|
getPageData?(pageId: string): Promise<ComponentModel | undefined>;
|
||||||
|
getLazyComponent?(pageId: string, props: any): any;
|
||||||
|
createApp?(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Provider implements IProvider {
|
||||||
|
private appKey = '';
|
||||||
|
private components: IComponents = {};
|
||||||
|
private utils: IUtils = {};
|
||||||
|
private constants: IConstants = {};
|
||||||
|
private routes: IRouterConfig | null = null;
|
||||||
|
private layout: ILayoutConfig | null = null;
|
||||||
|
private componentsMap: IComponentMap[] = [];
|
||||||
|
private history: HistoryMode = 'hash';
|
||||||
|
private containerId = '';
|
||||||
|
private lazyElementsMap: { [key: string]: any } = {};
|
||||||
|
|
||||||
|
constructor(options: IOptions) {
|
||||||
|
const { appKey } = options;
|
||||||
|
this.appKey = appKey;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async(): Promise<IAppConfig> {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const appData = await this.getAppData(this.appKey || '');
|
||||||
|
if (!appData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { history, layout, routes, containerId, components, componentsMap, utils, constants } = appData;
|
||||||
|
this.setHistory(history);
|
||||||
|
this.setLayoutConfig(layout);
|
||||||
|
this.setRouterConfig(routes);
|
||||||
|
this.setContainerId(containerId);
|
||||||
|
this.registerComponents(components);
|
||||||
|
this.registerComponentsMap(componentsMap);
|
||||||
|
this.registerUtils(utils);
|
||||||
|
this.registerContants(constants);
|
||||||
|
resolve({
|
||||||
|
history,
|
||||||
|
components,
|
||||||
|
utils,
|
||||||
|
containerId,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
reject(err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
console.log('init');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAppData(appkey: string): Promise<IAppData | undefined> {
|
||||||
|
throw new Error('Method called "getPageData" not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPageData(pageId: string): Promise<ComponentModel | undefined> {
|
||||||
|
throw new Error('Method called "getPageData" not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
getLazyComponent(pageId: string, props: any): any {
|
||||||
|
throw new Error('Method called "getLazyComponent" not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定制构造根组件的逻辑,如切换路由机制
|
||||||
|
createApp() {
|
||||||
|
throw new Error('Method called "createApp" not implemented.');
|
||||||
|
}
|
||||||
|
|
||||||
|
registerComponents(components: IComponents | undefined) {
|
||||||
|
if (!components) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.components = components;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerComponentsMap(componentsMap: IComponentMap[] | undefined) {
|
||||||
|
if (!componentsMap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.componentsMap = componentsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerUtils(utils: IUtils | undefined) {
|
||||||
|
if (!utils) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.utils = utils;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerContants(constants: IConstants | undefined) {
|
||||||
|
if (!constants) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.constants = constants;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLayoutConfig(config: ILayoutConfig | undefined) {
|
||||||
|
if (!config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.layout = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRouterConfig(config: IRouterConfig | undefined) {
|
||||||
|
if (!config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.routes = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHistory(config: HistoryMode | undefined) {
|
||||||
|
if (!config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.history = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
setContainerId(id: string | undefined) {
|
||||||
|
if (!id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.containerId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
setlazyElement(pageId: string, cache: any) {
|
||||||
|
if (!pageId || !cache) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.lazyElementsMap[pageId] = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
getComponents() {
|
||||||
|
return this.components;
|
||||||
|
}
|
||||||
|
|
||||||
|
getComponent(name: string) {
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return this.components[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
getUtils() {
|
||||||
|
return this.utils;
|
||||||
|
}
|
||||||
|
|
||||||
|
getConstants() {
|
||||||
|
return this.constants;
|
||||||
|
}
|
||||||
|
|
||||||
|
getComponentsMap() {
|
||||||
|
return this.componentsMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
getComponentsMapObj() {
|
||||||
|
const compMapArr = this.getComponentsMap();
|
||||||
|
if (!compMapArr || !Array.isArray(compMapArr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const compMapObj: any = {};
|
||||||
|
compMapArr.forEach((item: IComponentMap) => {
|
||||||
|
if (!item || !item.componentName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
compMapObj[item.componentName] = item;
|
||||||
|
});
|
||||||
|
return compMapObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLayoutConfig() {
|
||||||
|
return this.layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRouterConfig() {
|
||||||
|
return this.routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHistory() {
|
||||||
|
return this.history;
|
||||||
|
}
|
||||||
|
|
||||||
|
getContainerId() {
|
||||||
|
return this.containerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
getlazyElement(pageId: string) {
|
||||||
|
if (!pageId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return this.lazyElementsMap[pageId];
|
||||||
|
}
|
||||||
|
}
|
||||||
80
packages/runtime/src/core/provider/react/index.ts
Normal file
80
packages/runtime/src/core/provider/react/index.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { createElement, ReactElement } from 'react';
|
||||||
|
import { Router } from '@ali/recore';
|
||||||
|
import contribution from '../../contribution';
|
||||||
|
import Provider from '../base';
|
||||||
|
import LazyComponent from './lazy-component';
|
||||||
|
|
||||||
|
export default class ReactProvider extends Provider {
|
||||||
|
// 定制构造根组件的逻辑,如切换路由机制
|
||||||
|
createApp() {
|
||||||
|
const routerConfig = this.getRouterConfig();
|
||||||
|
if (!routerConfig) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const routes: Array<{ path: string; children: any; exact: boolean; keepAlive: boolean }> = [];
|
||||||
|
let homePageId = '';
|
||||||
|
Object.keys(routerConfig).forEach((pageId: string, idx: number) => {
|
||||||
|
if (!pageId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const path = 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.getComponents(),
|
||||||
|
utils: this.getUtils(),
|
||||||
|
componentsMap: this.getComponentsMapObj(),
|
||||||
|
...props,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
let App;
|
||||||
|
const layoutConfig = this.getLayoutConfig();
|
||||||
|
if (!layoutConfig || !layoutConfig.componentName) {
|
||||||
|
App = (props: any) => createElement(RouterView, { ...props });
|
||||||
|
return App;
|
||||||
|
}
|
||||||
|
const { componentName: layoutName, props: layoutProps } = layoutConfig;
|
||||||
|
const Layout = contribution.getLayout(layoutName);
|
||||||
|
if (Layout) {
|
||||||
|
App = (props: any) => createElement(Layout, layoutProps, RouterView({ props }));
|
||||||
|
} else {
|
||||||
|
App = (props: any) => createElement(RouterView, props);
|
||||||
|
}
|
||||||
|
return App;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { Component, createElement } from 'react';
|
import { Component, createElement } from 'react';
|
||||||
import boot from './boot';
|
import contribution from '../../contribution';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
getPageData: () => any;
|
getPageData: () => any;
|
||||||
@ -29,8 +29,8 @@ export default class LazyComponent extends Component<IProps, IState> {
|
|||||||
render() {
|
render() {
|
||||||
const { getPageData, ...restProps } = this.props;
|
const { getPageData, ...restProps } = this.props;
|
||||||
const { schema } = this.state;
|
const { schema } = this.state;
|
||||||
const Renderer = boot.getRenderer();
|
const Renderer = contribution.getRenderer();
|
||||||
const Loading = boot.getLoading();
|
const Loading = contribution.getLoading();
|
||||||
if (!Renderer || !schema) {
|
if (!Renderer || !schema) {
|
||||||
if (!Loading) {
|
if (!Loading) {
|
||||||
return null;
|
return null;
|
||||||
66
packages/runtime/src/core/run.ts
Normal file
66
packages/runtime/src/core/run.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { ReactType } from 'react';
|
||||||
|
import { runApp } from '@ali/recore';
|
||||||
|
import { HashHistoryBuildOptions, BrowserHistoryBuildOptions, MemoryHistoryBuildOptions } from '@recore/history';
|
||||||
|
import contribution from './contribution';
|
||||||
|
|
||||||
|
export type HistoryOptions = {
|
||||||
|
mode?: HistoryMode;
|
||||||
|
} & (HashHistoryBuildOptions | BrowserHistoryBuildOptions | MemoryHistoryBuildOptions);
|
||||||
|
|
||||||
|
export interface IComponents {
|
||||||
|
[key: string]: ReactType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUtils {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HistoryMode = 'browser' | 'hash';
|
||||||
|
|
||||||
|
export interface IAppConfig {
|
||||||
|
history?: HistoryMode;
|
||||||
|
components?: IComponents;
|
||||||
|
utils?: 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function run(config?: IAppConfig | (() => IAppConfig)) {
|
||||||
|
const provider = contribution.getProvider();
|
||||||
|
if (config) {
|
||||||
|
config = transformConfig(config);
|
||||||
|
const App = provider.createApp();
|
||||||
|
runApp(App, config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const promise = provider.async();
|
||||||
|
promise.then((config: IAppConfig) => {
|
||||||
|
if (!config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const App = provider.createApp();
|
||||||
|
config = transformConfig(config);
|
||||||
|
runApp(App, config);
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,5 +1,8 @@
|
|||||||
import { navigator, Router, runApp as run } from '@ali/recore';
|
import { navigator, Router } from '@ali/recore';
|
||||||
import boot from './boot';
|
import run from './core/run';
|
||||||
import Provider from './provider';
|
import contribution from './core/contribution';
|
||||||
|
import Provider from './core/provider/base';
|
||||||
|
import ReactProvider from './core/provider/react';
|
||||||
|
import * as Utils from './utils';
|
||||||
|
|
||||||
export { run, Router, boot, Provider, navigator };
|
export { run, Router, contribution, Provider, ReactProvider, navigator, Utils };
|
||||||
|
|||||||
@ -1,90 +0,0 @@
|
|||||||
import { createElement, ReactElement, ReactType } from 'react';
|
|
||||||
import LazyComponent from './lazyComponent';
|
|
||||||
|
|
||||||
export interface IAppData {
|
|
||||||
App: any;
|
|
||||||
config: object;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IComponentsMap {
|
|
||||||
[key: string]: ReactType;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IUtilsMap {
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
type HistoryMode = 'browser' | 'hash';
|
|
||||||
|
|
||||||
export interface IAppConfig {
|
|
||||||
history?: HistoryMode;
|
|
||||||
globalComponents?: IComponentsMap;
|
|
||||||
globalUtils?: IUtilsMap;
|
|
||||||
containerId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
create(appkey: string): Promise<IAppData> {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
try {
|
|
||||||
const config = await this.getAppData(appkey);
|
|
||||||
const App = this.createApp();
|
|
||||||
resolve({
|
|
||||||
App,
|
|
||||||
config,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
reject(err.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
console.log('init');
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAppData(appkey: string, restOptions?: any): Promise<object> {
|
|
||||||
console.log('getAppData');
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPageData(pageId: string, restOptions?: any): Promise<any> {
|
|
||||||
console.log('getPageData');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLazyComponent(pageId: string, props: any): ReactElement | null {
|
|
||||||
if (!pageId) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (this.lazyElementsMap[pageId]) {
|
|
||||||
console.log('缓存');
|
|
||||||
return this.lazyElementsMap[pageId];
|
|
||||||
} else {
|
|
||||||
const lazyElement = createElement(LazyComponent as any, {
|
|
||||||
getPageData: async () => await this.getPageData(pageId),
|
|
||||||
key: pageId,
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
this.lazyElementsMap[pageId] = lazyElement;
|
|
||||||
console.log('新组件');
|
|
||||||
return lazyElement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createApp() {
|
|
||||||
console.log('createApp');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +1,25 @@
|
|||||||
function isESModule(obj) {
|
function isESModule(obj: any): obj is { [key: string]: any } {
|
||||||
return obj && obj.__esModule;
|
return obj && obj.__esModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSubComponent(library, paths) {
|
function accessLibrary(library: string | object) {
|
||||||
|
if (typeof library !== 'string') {
|
||||||
|
return library;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (window as any)[library];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSubComponent(library: any, paths: string[]) {
|
||||||
const l = paths.length;
|
const l = paths.length;
|
||||||
if (l < 1 || !library) {
|
if (l < 1 || !library) {
|
||||||
return library;
|
return library;
|
||||||
}
|
}
|
||||||
let i = 0;
|
let i = 0;
|
||||||
let component;
|
let component: any;
|
||||||
while (i < l) {
|
while (i < l) {
|
||||||
const key = paths[i];
|
const key = paths[i]!;
|
||||||
let ex;
|
let ex: any;
|
||||||
try {
|
try {
|
||||||
component = library[key];
|
component = library[key];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -32,15 +40,7 @@ function getSubComponent(library, paths) {
|
|||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
function accessLibrary(library) {
|
function findComponent(libraryMap: LibraryMap, componentName: string, npm?: NpmInfo) {
|
||||||
if (typeof library !== 'string') {
|
|
||||||
return library;
|
|
||||||
}
|
|
||||||
|
|
||||||
return window[library];
|
|
||||||
}
|
|
||||||
|
|
||||||
function findComponent(libraryMap, componentName, npm) {
|
|
||||||
if (!npm) {
|
if (!npm) {
|
||||||
return accessLibrary(componentName);
|
return accessLibrary(componentName);
|
||||||
}
|
}
|
||||||
@ -62,10 +62,37 @@ function findComponent(libraryMap, componentName, npm) {
|
|||||||
return getSubComponent(library, paths);
|
return getSubComponent(library, paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildComponents(libraryMap, componentsMap) {
|
export interface LibraryMap {
|
||||||
const components = {};
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NpmInfo {
|
||||||
|
componentName?: string;
|
||||||
|
package: string;
|
||||||
|
version: string;
|
||||||
|
destructuring?: boolean;
|
||||||
|
exportName?: string;
|
||||||
|
subName?: string;
|
||||||
|
main?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildComponents(
|
||||||
|
libraryMap: LibraryMap,
|
||||||
|
componentsMap: { [componentName: string]: NpmInfo } | NpmInfo[],
|
||||||
|
) {
|
||||||
|
const components: any = {};
|
||||||
|
if (componentsMap && Array.isArray(componentsMap)) {
|
||||||
|
const compMapObj: any = {};
|
||||||
|
componentsMap.forEach((item: NpmInfo) => {
|
||||||
|
if (!item || !item.componentName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
compMapObj[item.componentName] = item;
|
||||||
|
});
|
||||||
|
componentsMap = compMapObj;
|
||||||
|
}
|
||||||
Object.keys(componentsMap).forEach((componentName) => {
|
Object.keys(componentsMap).forEach((componentName) => {
|
||||||
const component = findComponent(libraryMap, componentName, componentsMap[componentName]);
|
const component = findComponent(libraryMap, componentName, (componentsMap as any)[componentName]);
|
||||||
if (component) {
|
if (component) {
|
||||||
components[componentName] = component;
|
components[componentName] = component;
|
||||||
}
|
}
|
||||||
1
packages/runtime/src/utils/index.ts
Normal file
1
packages/runtime/src/utils/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './assets';
|
||||||
Loading…
x
Reference in New Issue
Block a user