Merge branch 'release/1.3.0'

This commit is contained in:
JackLian 2024-01-15 14:18:16 +08:00
commit f9fe38e65a
145 changed files with 1833 additions and 3000 deletions

2
.github/CODEOWNERS vendored
View File

@ -5,4 +5,4 @@
* @liujuping @JackLian
/modules/material-parser @akirakai
/modules/code-generator @leoyuan
/modules/code-generator @qingniaotonghua

View File

@ -33,7 +33,7 @@ jobs:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Get version
id: get_version
run: echo "::set-output name=version::$(node -p "require('./docs/package.json').version")"
run: echo "version=$(node -p "require('./docs/package.json').version")" >> $GITHUB_OUTPUT
comment-pr:
needs: publish-docs

View File

@ -27,4 +27,4 @@ jobs:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Get version
id: get_version
run: echo "::set-output name=version::$(node -p "require('./package.json').version")"
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT

View File

@ -30,4 +30,4 @@ jobs:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Get version
id: get_version
run: echo "::set-output name=version::$(node -p "require('./package.json').version")"
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT

2
.gitignore vendored
View File

@ -108,3 +108,5 @@ typings/
# codealike
codealike.json
.node
.must.config.js

View File

@ -145,6 +145,22 @@ const { intl, getLocale, setLocale } = common.utils.createIntl({
```
#### intl
i18n 转换方法
```typescript
/**
* i18n 转换方法
*/
intl(data: IPublicTypeI18nData | string, params?: object): string;
```
##### 示例
```
const title = common.utils.intl(node.title)
```
### skeletonCabin
#### Workbench
编辑器框架 View

210
docs/docs/api/commonUI.md Normal file
View File

@ -0,0 +1,210 @@
---
title: commonUI - UI 组件库
sidebar_position: 11
---
## 简介
CommonUI API 是一个专为低代码引擎设计的组件 UI 库,使用它开发的插件,可以保证在不同项目和主题切换中能够保持一致性和兼容性。
## 组件列表
### Tip
提示组件
| 参数 | 说明 | 类型 | 默认值 |
|-----------|--------------|---------------------------------------|--------|
| className | className | string (optional) | |
| children | tip 的内容 | IPublicTypeI18nData \| ReactNode | |
| direction | tip 的方向 | 'top' \| 'bottom' \| 'left' \| 'right' | |
### HelpTip
带 help icon 的提示组件
| 参数 | 说明 | 类型 | 默认值 |
|-----------|--------|-----------------------------------|--------|
| help | 描述 | IPublicTypeHelpTipConfig | |
| direction | 方向 | IPublicTypeTipConfig['direction'] | 'top' |
| size | 方向 | IconProps['size'] | 'small'|
### Title
标题组件
| 参数 | 说明 | 类型 | 默认值 |
|-----------|------------|-----------------------------|--------|
| title | 标题内容 | IPublicTypeTitleContent | |
| className | className | string (optional) | |
| onClick | 点击事件 | () => void (optional) | |
### ContextMenu
| 参数 | 说明 | 类型 | 默认值 |
|--------|----------------------------------------------------|------------------------------------|--------|
| menus | 定义上下文菜单的动作数组 | IPublicTypeContextMenuAction[] | |
| children | 组件的子元素 | React.ReactElement[] | |
**IPublicTypeContextMenuAction Interface**
| 参数 | 说明 | 类型 | 默认值 |
|------------|--------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|----------------------------------------|
| name | 动作的唯一标识符<br/>Unique identifier for the action | string | |
| title | 显示的标题,可以是字符串或国际化数据<br/>Display title, can be a string or internationalized data | string \| IPublicTypeI18nData (optional) | |
| type | 菜单项类型<br/>Menu item type | IPublicEnumContextMenuType (optional) | IPublicEnumContextMenuType.MENU_ITEM |
| action | 点击时执行的动作,可选<br/>Action to execute on click, optional | (nodes: IPublicModelNode[]) => void (optional) | |
| items | 子菜单项或生成子节点的函数,可选,仅支持两级<br/>Sub-menu items or function to generate child node, optional | Omit<IPublicTypeContextMenuAction, 'items'>[] \| ((nodes: IPublicModelNode[]) => Omit<IPublicTypeContextMenuAction, 'items'>[]) (optional) | |
| condition | 显示条件函数<br/>Function to determine display condition | (nodes: IPublicModelNode[]) => boolean (optional) | |
| disabled | 禁用条件函数,可选<br/>Function to determine disabled condition, optional | (nodes: IPublicModelNode[]) => boolean (optional) | |
**ContextMenu 示例**
```typescript
const App = () => {
const menuItems: IPublicTypeContextMenuAction[] = [
{
name: 'a',
title: '选项 1',
action: () => console.log('选项 1 被点击'),
},
{
name: 'b',
title: '选项 2',
action: () => console.log('选项 2 被点击'),
},
];
const ContextMenu = ctx.commonUI.ContextMenu;
return (
<div>
<ContextMenu menus={menuItems}>
<div>右键点击这里</div>
</ContextMenu>
</div>
);
};
export default App;
```
**ContextMenu.create 示例**
```typescript
const App = () => {
const menuItems: IPublicTypeContextMenuAction[] = [
{
name: 'a',
title: '选项 1',
action: () => console.log('选项 1 被点击'),
},
{
name: 'b',
title: '选项 2',
action: () => console.log('选项 2 被点击'),
},
];
const ContextMenu = ctx.commonUI.ContextMenu;
return (
<div>
<div onClick={(e) => {
ContextMenu.create(menuItems, e);
}}>点击这里</div>
</div>
);
};
export default App;
```
### Balloon
详细文档: [Balloon Documentation](https://fusion.design/pc/component/balloon)
### Breadcrumb
详细文档: [Breadcrumb Documentation](https://fusion.design/pc/component/breadcrumb)
### Button
详细文档: [Button Documentation](https://fusion.design/pc/component/button)
### Card
详细文档:[Card Documentation](https://fusion.design/pc/component/card)
### Checkbox
详细文档:[Checkbox Documentation](https://fusion.design/pc/component/checkbox)
### DatePicker
详细文档:[DatePicker Documentation](https://fusion.design/pc/component/datepicker)
### Dialog
详细文档:[Dialog Documentation](https://fusion.design/pc/component/dialog)
### Dropdown
详细文档:[Dropdown Documentation](https://fusion.design/pc/component/dropdown)
### Form
详细文档:[Form Documentation](https://fusion.design/pc/component/form)
### Icon
详细文档:[Icon Documentation](https://fusion.design/pc/component/icon)
引擎默认主题支持的 icon 列表https://fusion.design/64063/component/icon?themeid=20133
### Input
详细文档:[Input Documentation](https://fusion.design/pc/component/input)
### Loading
详细文档:[Loading Documentation](https://fusion.design/pc/component/loading)
### Message
详细文档:[Message Documentation](https://fusion.design/pc/component/message)
### Overlay
详细文档:[Overlay Documentation](https://fusion.design/pc/component/overlay)
### Pagination
详细文档:[Pagination Documentation](https://fusion.design/pc/component/pagination)
### Radio
详细文档:[Radio Documentation](https://fusion.design/pc/component/radio)
### Search
详细文档:[Search Documentation](https://fusion.design/pc/component/search)
### Select
详细文档:[Select Documentation](https://fusion.design/pc/component/select)
### SplitButton
详细文档:[SplitButton Documentation](https://fusion.design/pc/component/splitbutton)
### Step
详细文档:[Step Documentation](https://fusion.design/pc/component/step)
### Switch
详细文档:[Switch Documentation](https://fusion.design/pc/component/switch)
### Tab
详细文档:[Tab Documentation](https://fusion.design/pc/component/tab)
### Table
详细文档:[Table Documentation](https://fusion.design/pc/component/table)
### Tree
详细文档:[Tree Documentation](https://fusion.design/pc/component/tree)
### TreeSelect
详细文档:[TreeSelect Documentation](https://fusion.design/pc/component/treeselect)
### Upload
详细文档:[Upload Documentation](https://fusion.design/pc/component/upload)
### Divider
详细文档:[Divider Documentation](https://fusion.design/pc/component/divider)
## 说明
如果需要其他组件,可以提 issue 给我们。

View File

@ -185,6 +185,12 @@ config.set('enableCondition', false)
`@type {boolean}` `@default {false}`
#### enableContextMenu - 开启右键菜单
`@type {boolean}` `@default {false}`
是否开启右键菜单
#### disableDetecting
`@type {boolean}` `@default {false}`
@ -216,6 +222,12 @@ config.set('enableCondition', false)
是否在只有一个 item 的时候隐藏设置 tabs
#### hideComponentAction
`@type {boolean}` `@default {false}`
隐藏设计器辅助层
#### thisRequiredInJSE
`@type {boolean}` `@default {true}`

View File

@ -237,7 +237,90 @@ material.modifyBuiltinComponentAction('remove', (action) => {
});
```
### 右键菜单项
#### addContextMenuOption
添加右键菜单项
```typescript
/**
* 添加右键菜单项
* @param action
*/
addContextMenuOption(action: IPublicTypeContextMenuAction): void;
```
示例
```typescript
import { IPublicEnumContextMenuType } from '@alilc/lowcode-types';
material.addContextMenuOption({
name: 'parentItem',
title: 'Parent Item',
condition: (nodes) => true,
items: [
{
name: 'childItem1',
title: 'Child Item 1',
action: (nodes) => console.log('Child Item 1 clicked', nodes),
condition: (nodes) => true
},
// 分割线
{
type: IPublicEnumContextMenuType.SEPARATOR
name: 'separator.1'
}
// 更多子菜单项...
]
});
```
#### removeContextMenuOption
删除特定右键菜单项
```typescript
/**
* 删除特定右键菜单项
* @param name
*/
removeContextMenuOption(name: string): void;
```
#### adjustContextMenuLayout
调整右键菜单项布局,每次调用都会覆盖之前注册的调整函数,只有最后注册的函数会被应用。
```typescript
/**
* 调整右键菜单项布局
* @param actions
*/
adjustContextMenuLayout(fn: (actions: IPublicTypeContextMenuItem[]) => IPublicTypeContextMenuItem[]): void;
```
**示例**
通过 adjustContextMenuLayout 补充分割线
```typescript
material.adjustContextMenuLayout((actions: IPublicTypeContextMenuAction) => {
const names = ['a', 'b'];
const newActions = [];
actions.forEach(d => {
newActions.push(d);
if (names.include(d.name)) {
newActions.push({ type: 'separator' })
}
});
return newActions
})
```
### 物料元数据
#### getComponentMeta
获取指定名称的物料元数据

View File

@ -12,8 +12,6 @@ sidebar_position: 3
| @alilc/lowcode-engine | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/engine |
| @alilc/lowcode-plugin-designer | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/plugin-designer |
| @alilc/lowcode-plugin-outline-pane | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/plugin-outline-pane |
| @alilc/lowcode-rax-renderer | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/rax-renderer |
| @alilc/lowcode-rax-simulator-renderer | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/rax-simulator-renderer |
| @alilc/lowcode-react-renderer | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/react-renderer |
| @alilc/lowcode-react-simulator-renderer | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/react-simulator-renderer |
| @alilc/lowcode-renderer-core | [https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) | packages/renderer-core |

View File

@ -15,15 +15,13 @@ sidebar_position: 2
5. ignitor
6. plugin-designer
7. plugin-outline-pane
8. rax-renderer
9. rax-simulator-renderer
10. react-renderer
11. react-simulator-renderer
12. renderer-core
13. types
14. utils
15. material-parser
16. code-generator
8. react-renderer
9. react-simulator-renderer
10. renderer-core
11. types
12. utils
13. material-parser
14. code-generator
## 2. 引擎官方扩展包
包含了常用的设置器setter、跟 setter 绑定的插件等

View File

@ -11,7 +11,6 @@ sidebar_position: 4
## npm 包与仓库信息
- React 框架渲染 npm 包:@alilc/lowcode-react-renderer
- Rax 框架渲染 npm 包:@alilc/lowcode-rax-renderer
- 仓库:[https://github.com/alibaba/lowcode-engine](https://github.com/alibaba/lowcode-engine) 下的
- packages/renderer-core
- packages/react-renderer

View File

@ -53,6 +53,11 @@ sidebar_position: 9
- `--color-text-reverse`: 反色情况下,文字颜色
- `--color-text-disabled`: 禁用态文字颜色
#### 菜单颜色
- `--color-context-menu-text`: 菜单项颜色
- `--color-context-menu-text-hover`: 菜单项 hover 颜色
- `--color-context-menu-text-disabled`: 菜单项 disabled 颜色
#### 字段和边框颜色
- `--color-field-label`: field 标签颜色
@ -128,6 +133,7 @@ sidebar_position: 9
- `--pane-title-height`: 面板标题高度
- `--pane-title-font-size`: 面板标题字体大小
- `--pane-title-padding`: 面板标题边距
- `--context-menu-item-height`: 右键菜单项高度

View File

@ -40,7 +40,6 @@ ReactDOM.render((
), document.getElementById('root'));
```
- rax-renderernpm 包替换为 @alilc/lowcode-rax-renderer
####
### 项目使用示例
> [设计器 demo](https://lowcode-engine.cn/demo/demo-general/index.html)

View File

@ -47,15 +47,7 @@ npm install && npm run setup
[
"https?://uipaas-assets.com/prod/npm/@alilc/lowcode-engine/(.*)/dist/css/react-simulator-renderer.css",
"http://localhost:5555/css/ReactSimulatorRenderer.css"
],
[
"https?://uipaas-assets.com/prod/npm/@alilc/lowcode-engine/(.*)/dist/js/rax-simulator-renderer.js",
"http://localhost:5555/js/RaxSimulatorRenderer.js"
],
[
"https?://uipaas-assets.com/prod/npm/@alilc/lowcode-engine/(.*)/dist/css/rax-simulator-renderer.css",
"http://localhost:5555/css/RaxSimulatorRenderer.css"
],
]
]
}
```

View File

@ -499,7 +499,6 @@ try {
- 说明:组件即将从 DOM 中移除
- componentDidCatch(error, info)
- 说明:组件捕获到异常
- Rax目前没有使用生命周期使用 hooks 替代生命周期;
该对象由一系列 key-value 组成key 为生命周期方法名value 为 JSFunction 的描述,详见下方示例:

View File

@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-engine-docs",
"version": "1.2.17",
"version": "1.2.27",
"description": "低代码引擎版本化文档",
"license": "MIT",
"files": [

View File

@ -1,6 +1,6 @@
{
"lerna": "4.0.0",
"version": "1.2.5",
"version": "1.3.0",
"npmClient": "yarn",
"useWorkspaces": true,
"packages": [

View File

@ -94,16 +94,26 @@ await CodeGenerator.init();
4. 出码
```js
const result = await CodeGenerator.generateCode({
const project = await CodeGenerator.generateCode({
solution: 'icejs', // 出码方案 (目前内置有 icejs 和 rax )
schema, // 编排搭建出来的 schema
});
console.log(result); // 出码结果(默认是递归结构描述的,可以传 flattenResult: true 以生成扁平结构的结果)
console.log(project); // 出码结果(默认是递归结构描述的,可以传 flattenResult: true 以生成扁平结构的结果)
```
注:一般来说在浏览器中出码适合做即时预览功能。
5. 下载 zip 包
```js
// 写入到 zip 包
await CodeGenerator.publishers.zip().publish({
project, // 上一步生成的 project
projectSlug: 'your-project-slug', // 项目标识 -- 对应下载 your-project-slug.zip 文件
});
```
### 5自定义出码
前端框架灵活多变,默认内置的出码方案很难满足所有人的需求,好在此代码生成器支持非常灵活的插件机制 -- 欢迎参考 ./src/plugins/xxx 来编写您自己的出码插件,然后参考 ./src/solutions/xxx 将各种插件组合成一套适合您的业务场景的出码方案。

View File

@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-code-generator",
"version": "1.1.6",
"version": "1.1.7",
"description": "出码引擎 for LowCode Engine",
"license": "MIT",
"main": "lib/index.js",
@ -80,6 +80,7 @@
"change-case": "^3.1.0",
"commander": "^6.1.0",
"debug": "^4.3.2",
"file-saver": "^2.0.5",
"fp-ts": "^2.11.9",
"fs-extra": "9.x",
"glob": "^7.2.0",
@ -109,6 +110,7 @@
"devDependencies": {
"@iceworks/spec": "^1.4.2",
"@types/babel__traverse": "^7.11.0",
"@types/file-saver": "^2.0.7",
"@types/jest": "^27.0.2",
"@types/lodash": "^4.14.162",
"@types/node": "^14.14.20",

View File

@ -2,9 +2,9 @@ import { ResultDir } from '@alilc/lowcode-types';
import { PublisherFactory, IPublisher, IPublisherFactoryParams, PublisherError } from '../../types';
import { getErrorMessage } from '../../utils/errors';
import { isNodeProcess, writeZipToDisk, generateProjectZip } from './utils';
import { saveAs } from 'file-saver';
// export type ZipBuffer = Buffer | Blob;
export type ZipBuffer = Buffer;
export type ZipBuffer = Buffer | Blob;
declare type ZipPublisherResponse = string | ZipBuffer;
@ -44,10 +44,16 @@ export const createZipPublisher: PublisherFactory<ZipFactoryParams, ZipPublisher
try {
const zipContent = await generateProjectZip(projectToPublish);
// If not output path is provided, zip is not written to disk
const projectOutputPath = options.outputPath || outputPath;
if (projectOutputPath && isNodeProcess()) {
await writeZipToDisk(projectOutputPath, zipContent, zipName);
if (isNodeProcess()) {
// If not output path is provided on the node side, zip is not written to disk
const projectOutputPath = options.outputPath || outputPath;
if (projectOutputPath) {
await writeZipToDisk(projectOutputPath, zipContent, zipName);
}
} else {
// the browser end does not require a path
// auto download zip files
saveAs(zipContent as Blob, `${zipName}.zip`);
}
return { success: true, payload: zipContent };

View File

@ -40,8 +40,7 @@ export const writeZipToDisk = (
export const generateProjectZip = async (project: ResultDir): Promise<ZipBuffer> => {
let zip = new JSZip();
zip = writeFolderToZip(project, zip, true);
// const zipType = isNodeProcess() ? 'nodebuffer' : 'blob';
const zipType = 'nodebuffer'; // 目前先只支持 node 调用
const zipType = isNodeProcess() ? 'nodebuffer' : 'blob';
return zip.generateAsync({ type: zipType });
};

View File

@ -1,6 +1,14 @@
import CodeGen from '../../../../src';
import fileSaver from 'file-saver';
import * as utils from '../../../../src/publisher/zip/utils';
jest.mock('file-saver');
describe('public/publisher/zip/zip', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('should works', async () => {
const zip = CodeGen.publishers.zip({
outputPath: 'demo-output',
@ -19,15 +27,15 @@ describe('public/publisher/zip/zip', () => {
],
};
expect(zip.getOutputPath()).toMatchInlineSnapshot(`"demo-output"`);
expect(zip.getOutputPath()).toMatchInlineSnapshot('"demo-output"');
expect(zip.getProject()).toMatchInlineSnapshot(`undefined`);
expect(zip.getProject()).toMatchInlineSnapshot('undefined');
zip.setProject(demoProject);
expect(zip.getProject()).toBeTruthy();
expect(zip.getOutputPath()).toMatchInlineSnapshot(`"demo-output"`);
expect(zip.getOutputPath()).toMatchInlineSnapshot('"demo-output"');
expect(zip.setOutputPath('output')).toBe(undefined);
expect(zip.getOutputPath()).toMatchInlineSnapshot(`"output"`);
expect(zip.getOutputPath()).toMatchInlineSnapshot('"output"');
const publishRes = await zip.publish({
project: demoProject,
@ -41,4 +49,39 @@ describe('public/publisher/zip/zip', () => {
const zip = CodeGen.publishers.zip({});
expect(zip.publish()).rejects.toBeTruthy();
});
it('should publish the project as a zip file in the browser', async () => {
const zipContent = 'zip content';
const zipName = 'example-project';
jest.spyOn(utils, 'isNodeProcess').mockReturnValue(false);
// new Zip 里面也有平台判断,所以这里 mock
jest.spyOn(utils, 'generateProjectZip').mockResolvedValue(zipContent as any);
const spy = jest.spyOn(fileSaver, 'saveAs');
const zip = CodeGen.publishers.zip({
projectSlug: zipName,
});
const demoProject = {
name: 'demo',
dirs: [],
files: [
{
name: 'package',
ext: 'json',
content: '{ "name": "demo", "version": "1.0.0" }',
},
],
};
zip.setProject(demoProject);
const publishRes = await zip.publish({
project: demoProject,
});
expect(publishRes.success).toBeTruthy();
expect(spy).toBeCalledWith(zipContent, `${zipName}.zip`);
spy.mockReset();
spy.mockRestore();
});
});

View File

@ -54,7 +54,7 @@
"yarn": "^1.22.17",
"rimraf": "^3.0.2",
"@types/react-router": "5.1.18",
"@alilc/build-plugin-lce": "^0.0.3",
"@alilc/build-plugin-lce": "^0.0.4",
"babel-jest": "^26.5.2",
"@alilc/lowcode-test-mate": "^1.0.1"
},

View File

@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-designer",
"version": "1.2.5",
"version": "1.3.0",
"description": "Designer for Ali LowCode Engine",
"main": "lib/index.js",
"module": "es/index.js",
@ -15,9 +15,9 @@
},
"license": "MIT",
"dependencies": {
"@alilc/lowcode-editor-core": "1.2.5",
"@alilc/lowcode-types": "1.2.5",
"@alilc/lowcode-utils": "1.2.5",
"@alilc/lowcode-editor-core": "1.3.0",
"@alilc/lowcode-types": "1.3.0",
"@alilc/lowcode-utils": "1.3.0",
"classnames": "^2.2.6",
"react": "^16",
"react-dom": "^16.7.0",

View File

@ -9,7 +9,7 @@ import {
ComponentType,
} from 'react';
import classNames from 'classnames';
import { observer, computed, Tip } from '@alilc/lowcode-editor-core';
import { observer, computed, Tip, engineConfig } from '@alilc/lowcode-editor-core';
import { createIcon, isReactComponent, isActionContentObject } from '@alilc/lowcode-utils';
import { IPublicTypeActionContentObject } from '@alilc/lowcode-types';
import { BuiltinSimulatorHost } from '../host';
@ -47,14 +47,18 @@ export class BorderSelectingInstance extends Component<{
});
const { hideSelectTools } = observed.node.componentMeta.advanced;
const hideComponentAction = engineConfig.get('hideComponentAction');
if (hideSelectTools) {
return null;
}
return (
<div className={className} style={style}>
{!dragging && <Toolbar observed={observed} />}
<div
className={className}
style={style}
>
{(!dragging && !hideComponentAction) ? <Toolbar observed={observed} /> : null}
</div>
);
}

View File

@ -39,6 +39,7 @@ import {
isDragAnyObject,
isDragNodeObject,
isLocationData,
Logger,
} from '@alilc/lowcode-utils';
import {
isShaken,
@ -72,6 +73,8 @@ import { IScroller } from '../designer/scroller';
import { isElementNode, isDOMNodeVisible } from '../utils/misc';
import { debounce } from 'lodash';
const logger = new Logger({ level: 'warn', bizName: 'designer' });
export type LibraryItem = IPublicTypePackage & {
package: string;
library: string;
@ -122,21 +125,6 @@ const defaultSimulatorUrl = (() => {
return urls;
})();
const defaultRaxSimulatorUrl = (() => {
const publicPath = getPublicPath();
let urls;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, prefix = '', dev] = /^(.+?)(\/js)?\/?$/.exec(publicPath) || [];
if (dev) {
urls = [`${prefix}/css/rax-simulator-renderer.css`, `${prefix}/js/rax-simulator-renderer.js`];
} else if (process.env.NODE_ENV === 'production') {
urls = [`${prefix}/rax-simulator-renderer.css`, `${prefix}/rax-simulator-renderer.js`];
} else {
urls = [`${prefix}/rax-simulator-renderer.css`, `${prefix}/rax-simulator-renderer.js`];
}
return urls;
})();
const defaultEnvironment = [
// https://g.alicdn.com/mylib/??react/16.11.0/umd/react.production.min.js,react-dom/16.8.6/umd/react-dom.production.min.js,prop-types/15.7.2/prop-types.min.js
assetItem(
@ -151,17 +139,6 @@ const defaultEnvironment = [
),
];
const defaultRaxEnvironment = [
assetItem(
AssetType.JSText,
'window.Rax=parent.Rax;window.React=parent.React;window.ReactDOM=parent.ReactDOM;window.VisualEngineUtils=parent.VisualEngineUtils;window.VisualEngine=parent.VisualEngine',
),
assetItem(
AssetType.JSText,
'window.PropTypes=parent.PropTypes;React.PropTypes=parent.PropTypes; window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;',
),
];
export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProps> {
readonly isSimulator = true;
@ -467,11 +444,15 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
const libraryAsset: AssetList = this.buildLibrary();
if (this.renderEnv === 'rax') {
logger.error('After LowcodeEngine v1.3.0, Rax is no longer supported.');
}
const vendors = [
// required & use once
assetBundle(
this.get('environment') ||
(this.renderEnv === 'rax' ? defaultRaxEnvironment : defaultEnvironment),
defaultEnvironment,
AssetLevel.Environment,
),
// required & use once
@ -484,7 +465,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
// required & use once
assetBundle(
this.get('simulatorUrl') ||
(this.renderEnv === 'rax' ? defaultRaxSimulatorUrl : defaultSimulatorUrl),
defaultSimulatorUrl,
AssetLevel.Runtime,
),
];
@ -851,16 +832,22 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
doc.addEventListener('contextmenu', (e: MouseEvent) => {
const targetElement = e.target as HTMLElement;
const nodeInst = this.getNodeInstanceFromElement(targetElement);
const editor = this.designer?.editor;
if (!nodeInst) {
editor?.eventBus.emit('designer.builtinSimulator.contextmenu', {
originalEvent: e,
});
return;
}
const node = nodeInst.node || this.project.currentDocument?.focusNode;
if (!node) {
editor?.eventBus.emit('designer.builtinSimulator.contextmenu', {
originalEvent: e,
});
return;
}
// dirty code should refector
const editor = this.designer?.editor;
const npm = node?.componentMeta?.npm;
const selected =
[npm?.package, npm?.componentName].filter((item) => !!item).join('-') ||

View File

@ -48,13 +48,17 @@ export function buildFilter(rule?: string | string[] | RegExp | IPublicTypeNesti
return rule;
}
if (isRegExp(rule)) {
return (testNode: Node | IPublicTypeNodeSchema) => rule.test(testNode.componentName);
return (testNode: Node | IPublicTypeNodeSchema) => {
return rule.test(testNode.componentName);
};
}
const list = ensureAList(rule);
if (!list) {
return null;
}
return (testNode: Node | IPublicTypeNodeSchema) => list.includes(testNode.componentName);
return (testNode: Node | IPublicTypeNodeSchema) => {
return list.includes(testNode.componentName);
};
}
export interface IComponentMeta extends IPublicModelComponentMeta<INode> {

View File

@ -0,0 +1,10 @@
.engine-context-menu {
&.next-menu.next-ver .next-menu-item {
padding-right: 30px;
.next-menu-item-inner {
height: var(--context-menu-item-height, 30px);
line-height: var(--context-menu-item-height, 30px);
}
}
}

View File

@ -0,0 +1,233 @@
import { IPublicTypeContextMenuAction, IPublicEnumContextMenuType, IPublicTypeContextMenuItem, IPublicApiMaterial, IPublicModelPluginContext } from '@alilc/lowcode-types';
import { IDesigner, INode } from './designer';
import { createContextMenu, parseContextMenuAsReactNode, parseContextMenuProperties, uniqueId } from '@alilc/lowcode-utils';
import { Menu } from '@alifd/next';
import { engineConfig } from '@alilc/lowcode-editor-core';
import './context-menu-actions.scss';
export interface IContextMenuActions {
actions: IPublicTypeContextMenuAction[];
adjustMenuLayoutFn: (actions: IPublicTypeContextMenuItem[]) => IPublicTypeContextMenuItem[];
addMenuAction: IPublicApiMaterial['addContextMenuOption'];
removeMenuAction: IPublicApiMaterial['removeContextMenuOption'];
adjustMenuLayout: IPublicApiMaterial['adjustContextMenuLayout'];
}
let adjustMenuLayoutFn: Function = (actions: IPublicTypeContextMenuAction[]) => actions;
export class GlobalContextMenuActions {
enableContextMenu: boolean;
dispose: Function[];
contextMenuActionsMap: Map<string, ContextMenuActions> = new Map();
constructor() {
this.dispose = [];
engineConfig.onGot('enableContextMenu', (enable) => {
if (this.enableContextMenu === enable) {
return;
}
this.enableContextMenu = enable;
this.dispose.forEach(d => d());
if (enable) {
this.initEvent();
}
});
}
handleContextMenu = (
event: MouseEvent,
) => {
event.stopPropagation();
event.preventDefault();
const actions: IPublicTypeContextMenuAction[] = [];
let contextMenu: ContextMenuActions = this.contextMenuActionsMap.values().next().value;
this.contextMenuActionsMap.forEach((contextMenu) => {
actions.push(...contextMenu.actions);
});
let destroyFn: Function | undefined;
const destroy = () => {
destroyFn?.();
};
const pluginContext: IPublicModelPluginContext = contextMenu.designer.editor.get('pluginContext') as IPublicModelPluginContext;
const menus: IPublicTypeContextMenuItem[] = parseContextMenuProperties(actions, {
nodes: [],
destroy,
event,
pluginContext,
});
if (!menus.length) {
return;
}
const layoutMenu = adjustMenuLayoutFn(menus);
const menuNode = parseContextMenuAsReactNode(layoutMenu, {
destroy,
nodes: [],
pluginContext,
});
const target = event.target;
const { top, left } = target?.getBoundingClientRect();
const menuInstance = Menu.create({
target: event.target,
offset: [event.clientX - left, event.clientY - top],
children: menuNode,
className: 'engine-context-menu',
});
destroyFn = (menuInstance as any).destroy;
};
initEvent() {
this.dispose.push(
(() => {
const handleContextMenu = (e: MouseEvent) => {
this.handleContextMenu(e);
};
document.addEventListener('contextmenu', handleContextMenu);
return () => {
document.removeEventListener('contextmenu', handleContextMenu);
};
})(),
);
}
registerContextMenuActions(contextMenu: ContextMenuActions) {
this.contextMenuActionsMap.set(contextMenu.id, contextMenu);
}
}
const globalContextMenuActions = new GlobalContextMenuActions();
export class ContextMenuActions implements IContextMenuActions {
actions: IPublicTypeContextMenuAction[] = [];
designer: IDesigner;
dispose: Function[];
enableContextMenu: boolean;
id: string = uniqueId('contextMenu');;
constructor(designer: IDesigner) {
this.designer = designer;
this.dispose = [];
engineConfig.onGot('enableContextMenu', (enable) => {
if (this.enableContextMenu === enable) {
return;
}
this.enableContextMenu = enable;
this.dispose.forEach(d => d());
if (enable) {
this.initEvent();
}
});
globalContextMenuActions.registerContextMenuActions(this);
}
handleContextMenu = (
nodes: INode[],
event: MouseEvent,
) => {
const designer = this.designer;
event.stopPropagation();
event.preventDefault();
const actions = designer.contextMenuActions.actions;
const { bounds } = designer.project.simulator?.viewport || { bounds: { left: 0, top: 0 } };
const { left: simulatorLeft, top: simulatorTop } = bounds;
let destroyFn: Function | undefined;
const destroy = () => {
destroyFn?.();
};
const pluginContext: IPublicModelPluginContext = this.designer.editor.get('pluginContext') as IPublicModelPluginContext;
const menus: IPublicTypeContextMenuItem[] = parseContextMenuProperties(actions, {
nodes: nodes.map(d => designer.shellModelFactory.createNode(d)!),
destroy,
event,
pluginContext,
});
if (!menus.length) {
return;
}
const layoutMenu = adjustMenuLayoutFn(menus);
const menuNode = parseContextMenuAsReactNode(layoutMenu, {
destroy,
nodes: nodes.map(d => designer.shellModelFactory.createNode(d)!),
pluginContext,
});
destroyFn = createContextMenu(menuNode, {
event,
offset: [simulatorLeft, simulatorTop],
});
};
initEvent() {
const designer = this.designer;
this.dispose.push(
designer.editor.eventBus.on('designer.builtinSimulator.contextmenu', ({
node,
originalEvent,
}: {
node: INode;
originalEvent: MouseEvent;
}) => {
originalEvent.stopPropagation();
originalEvent.preventDefault();
// 如果右键的节点不在 当前选中的节点中,选中该节点
if (!designer.currentSelection.has(node.id)) {
designer.currentSelection.select(node.id);
}
const nodes = designer.currentSelection.getNodes();
this.handleContextMenu(nodes, originalEvent);
}),
);
}
addMenuAction(action: IPublicTypeContextMenuAction) {
this.actions.push({
type: IPublicEnumContextMenuType.MENU_ITEM,
...action,
});
}
removeMenuAction(name: string) {
const i = this.actions.findIndex((action) => action.name === name);
if (i > -1) {
this.actions.splice(i, 1);
}
}
adjustMenuLayout(fn: (actions: IPublicTypeContextMenuItem[]) => IPublicTypeContextMenuItem[]) {
adjustMenuLayoutFn = fn;
}
}

View File

@ -36,6 +36,10 @@ class Clipboard implements IClipboard {
private waitFn?: (data: any, e: ClipboardEvent) => void;
constructor() {
this.injectCopyPaster(document);
}
isCopyPasteEvent(e: Event) {
this.isCopyPaster(e.target);
}
@ -69,7 +73,13 @@ class Clipboard implements IClipboard {
}
const copyPaster = document.createElement<'textarea'>('textarea');
copyPaster.style.cssText = 'position: absolute;left: -9999px;top:-100px';
document.body.appendChild(copyPaster);
if (document.body) {
document.body.appendChild(copyPaster);
} else {
document.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(copyPaster);
});
}
const dispose = this.initCopyPaster(copyPaster);
return () => {
dispose();

View File

@ -4,7 +4,6 @@ import BuiltinDragGhostComponent from './drag-ghost';
import { Designer, DesignerProps } from './designer';
import { ProjectView } from '../project';
import './designer.less';
import { clipboard } from './clipboard';
type IProps = DesignerProps & {
designer?: Designer;
@ -44,7 +43,6 @@ export class DesignerView extends Component<IProps> {
if (onMount) {
onMount(this.designer);
}
clipboard.injectCopyPaster(document);
this.designer.postEvent('mount', this.designer);
}

View File

@ -20,7 +20,7 @@ import {
} from '@alilc/lowcode-types';
import { mergeAssets, IPublicTypeAssetsJson, isNodeSchema, isDragNodeObject, isDragNodeDataObject, isLocationChildrenDetail, Logger } from '@alilc/lowcode-utils';
import { IProject, Project } from '../project';
import { Node, DocumentModel, insertChildren, INode } from '../document';
import { Node, DocumentModel, insertChildren, INode, ISelection } from '../document';
import { ComponentMeta, IComponentMeta } from '../component-meta';
import { INodeSelector, Component } from '../simulator';
import { Scroller } from './scroller';
@ -32,6 +32,7 @@ import { OffsetObserver, createOffsetObserver } from './offset-observer';
import { ISettingTopEntry, SettingTopEntry } from './setting';
import { BemToolsManager } from '../builtin-simulator/bem-tools/manager';
import { ComponentActions } from '../component-actions';
import { ContextMenuActions, IContextMenuActions } from '../context-menu-actions';
const logger = new Logger({ level: 'warn', bizName: 'designer' });
@ -72,12 +73,16 @@ export interface IDesigner {
get componentActions(): ComponentActions;
get contextMenuActions(): ContextMenuActions;
get editor(): IPublicModelEditor;
get detecting(): Detecting;
get simulatorComponent(): ComponentType<any> | undefined;
get currentSelection(): ISelection;
createScroller(scrollable: IPublicTypeScrollable): IPublicModelScroller;
refreshComponentMetasMap(): void;
@ -122,6 +127,8 @@ export class Designer implements IDesigner {
readonly componentActions = new ComponentActions();
readonly contextMenuActions: IContextMenuActions;
readonly activeTracker = new ActiveTracker();
readonly detecting = new Detecting();
@ -198,6 +205,8 @@ export class Designer implements IDesigner {
this.postEvent('dragstart', e);
});
this.contextMenuActions = new ContextMenuActions(this);
this.dragon.onDrag((e) => {
if (this.props?.onDrag) {
this.props.onDrag(e);

View File

@ -1,4 +1,4 @@
import { IPublicTypeCustomView, IPublicModelEditor, IPublicModelSettingTopEntry } from '@alilc/lowcode-types';
import { IPublicTypeCustomView, IPublicModelEditor, IPublicModelSettingTopEntry, IPublicApiSetters } from '@alilc/lowcode-types';
import { isCustomView } from '@alilc/lowcode-utils';
import { computed, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core';
import { ISettingEntry } from './setting-entry-type';
@ -6,7 +6,6 @@ import { ISettingField, SettingField } from './setting-field';
import { INode } from '../../document';
import type { IComponentMeta } from '../../component-meta';
import { IDesigner } from '../designer';
import { Setters } from '@alilc/lowcode-shell';
function generateSessionId(nodes: INode[]) {
return nodes
@ -19,18 +18,18 @@ export interface ISettingTopEntry extends ISettingEntry, IPublicModelSettingTopE
INode,
ISettingField
> {
purge(): void;
items: Array<ISettingField | IPublicTypeCustomView>;
readonly top: ISettingTopEntry;
readonly parent: ISettingTopEntry;
readonly path: never[];
items: Array<ISettingField | IPublicTypeCustomView>;
componentMeta: IComponentMeta | null;
purge(): void;
getExtraPropValue(propName: string): void;
setExtraPropValue(propName: string, value: any): void;
@ -92,7 +91,7 @@ export class SettingTopEntry implements ISettingTopEntry {
readonly designer: IDesigner | undefined;
readonly setters: Setters;
readonly setters: IPublicApiSetters;
disposeFunctions: any[] = [];
@ -103,7 +102,7 @@ export class SettingTopEntry implements ISettingTopEntry {
this.id = generateSessionId(nodes);
this.first = nodes[0];
this.designer = this.first.document?.designer;
this.setters = editor.get('setters') as Setters;
this.setters = editor.get('setters') as IPublicApiSetters;
// setups
this.setupComponentMeta();

View File

@ -392,7 +392,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
this.isInited = true;
this.emitter = createModuleEventBus('Node');
const editor = this.document.designer.editor;
const { editor } = this.document.designer;
this.onVisibleChange((visible: boolean) => {
editor?.eventBus.emit(EDITOR_EVENT.NODE_VISIBLE_CHANGE, this, visible);
});
@ -1219,11 +1219,18 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/**
*
*/
getRGL() {
getRGL(): {
isContainerNode: boolean;
isEmptyNode: boolean;
isRGLContainerNode: boolean;
isRGLNode: boolean;
isRGL: boolean;
rglNode: Node | null;
} {
const isContainerNode = this.isContainer();
const isEmptyNode = this.isEmpty();
const isRGLContainerNode = this.isRGLContainer;
const isRGLNode = this.getParent()?.isRGLContainer;
const isRGLNode = (this.getParent()?.isRGLContainer) as boolean;
const isRGL = isRGLContainerNode || (isRGLNode && (!isContainerNode || !isEmptyNode));
let rglNode = isRGLContainerNode ? this : isRGL ? this?.getParent() : null;
return { isContainerNode, isEmptyNode, isRGLContainerNode, isRGLNode, isRGL, rglNode };

View File

@ -6,3 +6,4 @@ export * from './project';
export * from './builtin-simulator';
export * from './plugin';
export * from './types';
export * from './context-menu-actions';

View File

@ -19,6 +19,7 @@ import {
IPublicApiWorkspace,
IPublicEnumPluginRegisterLevel,
IPublicModelWindow,
IPublicApiCommonUI,
} from '@alilc/lowcode-types';
import {
IPluginContextOptions,
@ -45,6 +46,8 @@ export default class PluginContext implements
workspace: IPublicApiWorkspace;
registerLevel: IPublicEnumPluginRegisterLevel;
editorWindow: IPublicModelWindow;
commonUI: IPublicApiCommonUI;
isPluginRegisteredInWorkspace: false;
constructor(
options: IPluginContextOptions,

View File

@ -18,6 +18,7 @@ import {
IPublicTypePluginRegisterOptions,
IPublicModelWindow,
IPublicEnumPluginRegisterLevel,
IPublicApiCommonUI,
} from '@alilc/lowcode-types';
import PluginContext from './plugin-context';
@ -61,6 +62,7 @@ export interface ILowCodePluginContextPrivate {
set editorWindow(window: IPublicModelWindow);
set registerLevel(level: IPublicEnumPluginRegisterLevel);
set isPluginRegisteredInWorkspace(flag: boolean);
set commonUI(commonUI: IPublicApiCommonUI);
}
export interface ILowCodePluginContextApiAssembler {
assembleApis(

View File

@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-editor-core",
"version": "1.2.5",
"version": "1.3.0",
"description": "Core Api for Ali lowCode engine",
"license": "MIT",
"main": "lib/index.js",
@ -14,8 +14,8 @@
},
"dependencies": {
"@alifd/next": "^1.19.16",
"@alilc/lowcode-types": "1.2.5",
"@alilc/lowcode-utils": "1.2.5",
"@alilc/lowcode-types": "1.3.0",
"@alilc/lowcode-utils": "1.3.0",
"classnames": "^2.2.6",
"debug": "^4.1.1",
"intl-messageformat": "^9.3.1",

View File

@ -44,7 +44,7 @@ const VALID_ENGINE_OPTIONS = {
},
renderEnv: {
type: 'string',
enum: ['react', 'rax', 'any string value'],
enum: ['react', 'any string value'],
default: 'react',
description: '渲染器类型',
},
@ -159,6 +159,16 @@ const VALID_ENGINE_OPTIONS = {
type: 'function',
description: '应用级设计模式下,窗口为空时展示的占位组件',
},
enableContextMenu: {
type: 'boolean',
description: '是否开启右键菜单',
default: false,
},
hideComponentAction: {
type: 'boolean',
description: '是否隐藏设计器辅助层',
default: false,
},
};
const getStrictModeValue = (engineOptions: IPublicTypeEngineOptions, defaultValue: boolean): boolean => {

View File

@ -3,6 +3,7 @@ import { IntlMessageFormat } from 'intl-messageformat';
import { globalLocale } from './global-locale';
import { isI18nData } from '@alilc/lowcode-utils';
import { observer } from '../utils';
import { IPublicTypeI18nData } from '@alilc/lowcode-types';
function generateTryLocales(locale: string) {
const tries = [locale, locale.replace('-', '_')];
@ -26,18 +27,9 @@ function injectVars(msg: string, params: any, locale: string): string {
}
const formater = new IntlMessageFormat(msg, locale);
return formater.format(params as any) as string;
/*
return template.replace(/({\w+})/g, (_, $1) => {
const key = (/\d+/.exec($1) || [])[0] as any;
if (key && params[key] != null) {
return params[key];
}
return $1;
}); */
}
export function intl(data: any, params?: object): ReactNode {
export function intl(data: IPublicTypeI18nData | string, params?: object): ReactNode {
if (!isI18nData(data)) {
return data;
}

View File

@ -0,0 +1,40 @@
import { IPublicTypeHelpTipConfig, IPublicTypeTipConfig } from '@alilc/lowcode-types';
import { Tip } from './tip';
import { Icon } from '@alifd/next';
import { IconProps } from '@alifd/next/types/icon';
export function HelpTip({
help,
direction = 'top',
size = 'small',
}: {
help: IPublicTypeHelpTipConfig;
direction?: IPublicTypeTipConfig['direction'];
size?: IconProps['size'];
}) {
if (typeof help === 'string') {
return (
<div>
<Icon type="help" size={size} className="lc-help-tip" />
<Tip direction={direction}>{help}</Tip>
</div>
);
}
if (typeof help === 'object' && help.url) {
return (
<div>
<a href={help.url} target="_blank" rel="noopener noreferrer">
<Icon type="help" size={size} className="lc-help-tip" />
</a>
<Tip direction={direction}>{help.content}</Tip>
</div>
);
}
return (
<div>
<Icon type="help" size="small" className="lc-help-tip" />
<Tip direction={direction}>{help.content}</Tip>
</div>
);
}

View File

@ -2,3 +2,4 @@ import './style.less';
export * from './tip';
export * from './tip-container';
export * from './help-tips';

View File

@ -1,7 +1,7 @@
import { Component, isValidElement, ReactNode } from 'react';
import classNames from 'classnames';
import { createIcon, isI18nData, isTitleConfig } from '@alilc/lowcode-utils';
import { IPublicTypeTitleContent, IPublicTypeI18nData, IPublicTypeTitleConfig } from '@alilc/lowcode-types';
import { IPublicTypeI18nData, IPublicTypeTitleConfig, IPublicTypeTitleProps } from '@alilc/lowcode-types';
import { intl } from '../../intl';
import { Tip } from '../tip';
import './title.less';
@ -36,13 +36,7 @@ import './title.less';
return fragments;
}
export class Title extends Component<{
title: IPublicTypeTitleContent;
className?: string;
onClick?: () => void;
match?: boolean;
keywords?: string;
}> {
export class Title extends Component<IPublicTypeTitleProps> {
constructor(props: any) {
super(props);
this.handleClick = this.handleClick.bind(this);

View File

@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-editor-skeleton",
"version": "1.2.5",
"version": "1.3.0",
"description": "alibaba lowcode editor skeleton",
"main": "lib/index.js",
"module": "es/index.js",
@ -19,10 +19,10 @@
],
"dependencies": {
"@alifd/next": "^1.20.12",
"@alilc/lowcode-designer": "1.2.5",
"@alilc/lowcode-editor-core": "1.2.5",
"@alilc/lowcode-types": "1.2.5",
"@alilc/lowcode-utils": "1.2.5",
"@alilc/lowcode-designer": "1.3.0",
"@alilc/lowcode-editor-core": "1.3.0",
"@alilc/lowcode-types": "1.3.0",
"@alilc/lowcode-utils": "1.3.0",
"classnames": "^2.2.6",
"react": "^16.8.1",
"react-dom": "^16.8.1"

View File

@ -1,7 +1,6 @@
import { Component, ReactElement } from 'react';
import { Icon } from '@alifd/next';
import classNames from 'classnames';
import { Title, observer, Tip } from '@alilc/lowcode-editor-core';
import { Title, observer, HelpTip } from '@alilc/lowcode-editor-core';
import { DockProps } from '../../types';
import { PanelDock } from '../../widget/panel-dock';
import { composeTitle } from '../../widget/utils';
@ -26,25 +25,6 @@ export function DockView({ title, icon, description, size, className, onClick }:
);
}
function HelpTip({ tip }: any) {
if (tip && tip.url) {
return (
<div>
<a href={tip.url} target="_blank" rel="noopener noreferrer">
<Icon type="help" size="small" className="lc-help-tip" />
</a>
<Tip>{tip.content}</Tip>
</div>
);
}
return (
<div>
<Icon type="help" size="small" className="lc-help-tip" />
<Tip>{tip.content}</Tip>
</div>
);
}
@observer
export class PanelDockView extends Component<DockProps & { dock: PanelDock }> {
private lastActived = false;
@ -328,7 +308,7 @@ class PanelTitle extends Component<{ panel: Panel; className?: string }> {
data-name={panel.name}
>
<Title title={panel.title || panel.name} />
{panel.help ? <HelpTip tip={panel.help} /> : null}
{panel.help ? <HelpTip help={panel.help} /> : null}
</div>
);
}

View File

@ -126,7 +126,7 @@ https://cdn.jsdelivr.net/npm/@alilc/lowcode-react-simulator-renderer@1.0.18/dist
```
#### 方式 5使用自有 cdn
将源码中 packages/engine/dist 和 packages/(react|rax)-simulator-renderer/dist 下的文件传至你的 cdn 提供商
将源码中 packages/engine/dist 和 packages/react-simulator-renderer/dist 下的文件传至你的 cdn 提供商
## 🔗 相关链接

View File

@ -126,7 +126,7 @@ https://cdn.jsdelivr.net/npm/@alilc/lowcode-react-simulator-renderer@1.0.18/dist
```
#### Method 5: Use your own cdn
Pass the files under packages/engine/dist and packages/(react|rax)-simulator-renderer/dist in the source code to your cdn provider
Pass the files under packages/engine/dist and packages/react-simulator-renderer/dist in the source code to your cdn provider
## 🔗 Related Links

View File

@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-engine",
"version": "1.2.5",
"version": "1.3.0",
"description": "An enterprise-class low-code technology stack with scale-out design / 一套面向扩展设计的企业级低代码技术体系",
"main": "lib/engine-core.js",
"module": "es/engine-core.js",
@ -19,15 +19,15 @@
"license": "MIT",
"dependencies": {
"@alifd/next": "^1.19.12",
"@alilc/lowcode-designer": "1.2.5",
"@alilc/lowcode-editor-core": "1.2.5",
"@alilc/lowcode-editor-skeleton": "1.2.5",
"@alilc/lowcode-designer": "1.3.0",
"@alilc/lowcode-editor-core": "1.3.0",
"@alilc/lowcode-editor-skeleton": "1.3.0",
"@alilc/lowcode-engine-ext": "^1.0.0",
"@alilc/lowcode-plugin-designer": "1.2.5",
"@alilc/lowcode-plugin-outline-pane": "1.2.5",
"@alilc/lowcode-shell": "1.2.5",
"@alilc/lowcode-utils": "1.2.5",
"@alilc/lowcode-workspace": "1.2.5",
"@alilc/lowcode-plugin-designer": "1.3.0",
"@alilc/lowcode-plugin-outline-pane": "1.3.0",
"@alilc/lowcode-shell": "1.3.0",
"@alilc/lowcode-utils": "1.3.0",
"@alilc/lowcode-workspace": "1.3.0",
"react": "^16.8.1",
"react-dom": "^16.8.1"
},

View File

@ -51,6 +51,7 @@ import {
Canvas,
Workspace,
Config,
CommonUI,
} from '@alilc/lowcode-shell';
import { isPlainObject } from '@alilc/lowcode-utils';
import './modules/live-editing';
@ -61,6 +62,7 @@ import { setterRegistry } from './inner-plugins/setter-registry';
import { defaultPanelRegistry } from './inner-plugins/default-panel-registry';
import { shellModelFactory } from './modules/shell-model-factory';
import { builtinHotkey } from './inner-plugins/builtin-hotkey';
import { defaultContextMenu } from './inner-plugins/default-context-menu';
import { OutlinePlugin } from '@alilc/lowcode-plugin-outline-pane';
export * from './modules/skeleton-types';
@ -77,6 +79,7 @@ async function registryInnerPlugin(designer: IDesigner, editor: IEditor, plugins
await plugins.register(defaultPanelRegistryPlugin);
await plugins.register(builtinHotkey);
await plugins.register(registerDefaults, {}, { autoInit: true });
await plugins.register(defaultContextMenu);
return () => {
plugins.delete(OutlinePlugin.pluginName);
@ -85,6 +88,7 @@ async function registryInnerPlugin(designer: IDesigner, editor: IEditor, plugins
plugins.delete(defaultPanelRegistryPlugin.pluginName);
plugins.delete(builtinHotkey.pluginName);
plugins.delete(registerDefaults.pluginName);
plugins.delete(defaultContextMenu.pluginName);
};
}
@ -111,6 +115,7 @@ const innerSetters = new InnerSetters();
const setters = new Setters(innerSetters);
const material = new Material(editor);
const commonUI = new CommonUI(editor);
editor.set('project', project);
editor.set('setters' as any, setters);
editor.set('material', material);
@ -138,8 +143,10 @@ const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
context.plugins = plugins;
context.logger = new Logger({ level: 'warn', bizName: `plugin:${pluginName}` });
context.workspace = workspace;
context.commonUI = commonUI;
context.registerLevel = IPublicEnumPluginRegisterLevel.Default;
context.isPluginRegisteredInWorkspace = false;
editor.set('pluginContext', context);
},
};
@ -161,6 +168,7 @@ export {
common,
workspace,
canvas,
commonUI,
};
// declare this is open-source version
export const isOpenSource = true;

View File

@ -0,0 +1,223 @@
import {
IPublicEnumContextMenuType,
IPublicEnumDragObjectType,
IPublicEnumTransformStage,
IPublicModelNode,
IPublicModelPluginContext,
IPublicTypeDragNodeDataObject,
IPublicTypeNodeSchema,
} from '@alilc/lowcode-types';
import { isProjectSchema } from '@alilc/lowcode-utils';
import { Message } from '@alifd/next';
import { intl } from '../locale';
function getNodesSchema(nodes: IPublicModelNode[]) {
const componentsTree = nodes.map((node) => node?.exportSchema(IPublicEnumTransformStage.Clone));
const data = { type: 'nodeSchema', componentsMap: {}, componentsTree };
return data;
}
async function getClipboardText(): Promise<IPublicTypeNodeSchema[]> {
return new Promise((resolve, reject) => {
// 使用 Clipboard API 读取剪贴板内容
navigator.clipboard.readText().then(
(text) => {
try {
const data = JSON.parse(text);
if (isProjectSchema(data)) {
resolve(data.componentsTree);
} else {
Message.error(intl('NotValidNodeData'));
reject(
new Error(intl('NotValidNodeData')),
);
}
} catch (error) {
Message.error(intl('NotValidNodeData'));
reject(error);
}
},
(err) => {
reject(err);
},
);
});
}
export const defaultContextMenu = (ctx: IPublicModelPluginContext) => {
const { material, canvas, common } = ctx;
const { clipboard } = canvas;
const { intl: utilsIntl } = common.utils;
return {
init() {
material.addContextMenuOption({
name: 'selectComponent',
title: intl('SelectComponents'),
condition: (nodes = []) => {
return nodes.length === 1;
},
items: [
{
name: 'nodeTree',
type: IPublicEnumContextMenuType.NODE_TREE,
},
],
});
material.addContextMenuOption({
name: 'copyAndPaste',
title: intl('CopyAndPaste'),
disabled: (nodes = []) => {
return nodes?.filter((node) => !node?.canPerformAction('copy')).length > 0;
},
condition: (nodes) => {
return nodes?.length === 1;
},
action(nodes) {
const node = nodes?.[0];
if (!node) {
return;
}
const { document: doc, parent, index } = node;
const data = getNodesSchema(nodes);
clipboard.setData(data);
if (parent) {
const newNode = doc?.insertNode(parent, node, (index ?? 0) + 1, true);
newNode?.select();
}
},
});
material.addContextMenuOption({
name: 'copy',
title: intl('Copy'),
disabled: (nodes = []) => {
return nodes?.filter((node) => !node?.canPerformAction('copy')).length > 0;
},
condition(nodes = []) {
return nodes?.length > 0;
},
action(nodes) {
if (!nodes || nodes.length < 1) {
return;
}
const data = getNodesSchema(nodes);
clipboard.setData(data);
},
});
material.addContextMenuOption({
name: 'pasteToBottom',
title: intl('PasteToTheBottom'),
condition: (nodes) => {
return nodes?.length === 1;
},
async action(nodes) {
if (!nodes || nodes.length < 1) {
return;
}
const node = nodes[0];
const { document: doc, parent, index } = node;
try {
const nodeSchema = await getClipboardText();
if (nodeSchema.length === 0) {
return;
}
if (parent) {
let canAddNodes = nodeSchema.filter((nodeSchema: IPublicTypeNodeSchema) => {
const dragNodeObject: IPublicTypeDragNodeDataObject = {
type: IPublicEnumDragObjectType.NodeData,
data: nodeSchema,
};
return doc?.checkNesting(parent, dragNodeObject);
});
if (canAddNodes.length === 0) {
Message.error(`${nodeSchema.map(d => utilsIntl(d.title || d.componentName)).join(',')}等组件无法放置到${utilsIntl(parent.title || parent.componentName as any)}`);
return;
}
const nodes: IPublicModelNode[] = [];
canAddNodes.forEach((schema, schemaIndex) => {
const node = doc?.insertNode(parent, schema, (index ?? 0) + 1 + schemaIndex, true);
node && nodes.push(node);
});
doc?.selection.selectAll(nodes.map((node) => node?.id));
}
} catch (error) {
console.error(error);
}
},
});
material.addContextMenuOption({
name: 'pasteToInner',
title: intl('PasteToTheInside'),
condition: (nodes) => {
return nodes?.length === 1;
},
disabled: (nodes = []) => {
// 获取粘贴数据
const node = nodes?.[0];
return !node.isContainerNode;
},
async action(nodes) {
const node = nodes?.[0];
if (!node) {
return;
}
const { document: doc } = node;
try {
const nodeSchema = await getClipboardText();
const index = node.children?.size || 0;
if (nodeSchema.length === 0) {
return;
}
let canAddNodes = nodeSchema.filter((nodeSchema: IPublicTypeNodeSchema) => {
const dragNodeObject: IPublicTypeDragNodeDataObject = {
type: IPublicEnumDragObjectType.NodeData,
data: nodeSchema,
};
return doc?.checkNesting(node, dragNodeObject);
});
if (canAddNodes.length === 0) {
Message.error(`${nodeSchema.map(d => utilsIntl(d.title || d.componentName)).join(',')}等组件无法放置到${utilsIntl(node.title || node.componentName as any)}`);
return;
}
const nodes: IPublicModelNode[] = [];
nodeSchema.forEach((schema, schemaIndex) => {
const newNode = doc?.insertNode(node, schema, (index ?? 0) + 1 + schemaIndex, true);
newNode && nodes.push(newNode);
});
doc?.selection.selectAll(nodes.map((node) => node?.id));
} catch (error) {
console.error(error);
}
},
});
material.addContextMenuOption({
name: 'delete',
title: intl('Delete'),
disabled(nodes = []) {
return nodes?.filter((node) => !node?.canPerformAction('remove')).length > 0;
},
condition(nodes = []) {
return nodes.length > 0;
},
action(nodes) {
nodes?.forEach((node) => {
node.remove();
});
},
});
},
};
};
defaultContextMenu.pluginName = '___default_context_menu___';

View File

@ -0,0 +1,9 @@
{
"NotValidNodeData": "Not valid node data",
"SelectComponents": "Select components",
"CopyAndPaste": "Copy and Paste",
"Copy": "Copy",
"PasteToTheBottom": "Paste to the bottom",
"PasteToTheInside": "Paste to the inside",
"Delete": "Delete"
}

View File

@ -0,0 +1,14 @@
import { createIntl } from '@alilc/lowcode-editor-core';
import enUS from './en-US.json';
import zhCN from './zh-CN.json';
const { intl, getLocale } = createIntl?.({
'en-US': enUS,
'zh-CN': zhCN,
}) || {
intl: (id) => {
return zhCN[id];
},
};
export { intl, enUS, zhCN, getLocale };

View File

@ -0,0 +1,9 @@
{
"NotValidNodeData": "不是有效的节点数据",
"SelectComponents": "选择组件",
"CopyAndPaste": "复制",
"Copy": "拷贝",
"PasteToTheBottom": "粘贴至下方",
"PasteToTheInside": "粘贴至内部",
"Delete": "删除"
}

View File

@ -1,8 +1,7 @@
{
"entry": {
"AliLowCodeEngine": "../engine/src/index.ts",
"ReactSimulatorRenderer": "../react-simulator-renderer/src/index.ts",
"RaxSimulatorRenderer": "../rax-simulator-renderer/src/index.ts"
"ReactSimulatorRenderer": "../react-simulator-renderer/src/index.ts"
},
"vendor": false,
"devServer": {

View File

@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-ignitor",
"version": "1.2.5",
"version": "1.3.0",
"description": "点火器bootstrap lce project",
"main": "lib/index.js",
"private": true,

View File

@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-plugin-designer",
"version": "1.2.5",
"version": "1.3.0",
"description": "alibaba lowcode editor designer plugin",
"files": [
"es",
@ -18,9 +18,9 @@
],
"author": "xiayang.xy",
"dependencies": {
"@alilc/lowcode-designer": "1.2.5",
"@alilc/lowcode-editor-core": "1.2.5",
"@alilc/lowcode-utils": "1.2.5",
"@alilc/lowcode-designer": "1.3.0",
"@alilc/lowcode-editor-core": "1.3.0",
"@alilc/lowcode-utils": "1.3.0",
"react": "^16.8.1",
"react-dom": "^16.8.1"
},

View File

@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-plugin-outline-pane",
"version": "1.2.5",
"version": "1.3.0",
"description": "Outline pane for Ali lowCode engine",
"files": [
"es",
@ -13,8 +13,8 @@
},
"dependencies": {
"@alifd/next": "^1.19.16",
"@alilc/lowcode-types": "1.2.5",
"@alilc/lowcode-utils": "1.2.5",
"@alilc/lowcode-types": "1.3.0",
"@alilc/lowcode-utils": "1.3.0",
"classnames": "^2.2.6",
"react": "^16",
"react-dom": "^16.7.0",

View File

@ -77,6 +77,11 @@ export const matchTreeNode = (
return matchTreeNode(childNode, keywords, filterOps);
}).find(Boolean);
// 如果命中了子节点,需要将该节点展开
if (matchChild && treeNode.expandable) {
treeNode.setExpanded(true);
}
treeNode.setFilterReult({
filterWorking: true,
matchChild,

View File

@ -356,7 +356,7 @@
// 选中节点处理
&.selected {
& > .tree-node-title {
background: var(--color-block-background-shallow);
background: var(--color-block-background-light);
}
& > .tree-node-branches::before {

View File

@ -34,7 +34,7 @@ class ModalTreeNodeView extends PureComponent<{
}
componentDidMount(): void {
const rootTreeNode = this.rootTreeNode;
const { rootTreeNode } = this;
rootTreeNode.onExpandableChanged(() => {
this.setState({
treeChildren: rootTreeNode.children,
@ -53,7 +53,7 @@ class ModalTreeNodeView extends PureComponent<{
}
render() {
const rootTreeNode = this.rootTreeNode;
const { rootTreeNode } = this;
const { expanded } = rootTreeNode;
const hasVisibleModalNode = !!this.modalNodesManager?.getVisibleModalNode();
@ -98,6 +98,9 @@ export default class TreeNodeView extends PureComponent<{
conditionFlow: boolean;
expandable: boolean;
treeChildren: TreeNode[] | null;
filterWorking: boolean;
matchChild: boolean;
matchSelf: boolean;
} = {
expanded: false,
selected: false,
@ -110,6 +113,9 @@ export default class TreeNodeView extends PureComponent<{
conditionFlow: false,
expandable: false,
treeChildren: [],
filterWorking: false,
matchChild: false,
matchSelf: false,
};
eventOffCallbacks: Array<IPublicTypeDisposable | undefined> = [];
@ -154,6 +160,10 @@ export default class TreeNodeView extends PureComponent<{
treeChildren: treeNode.children,
});
});
treeNode.onFilterResultChanged(() => {
const { filterWorking: newFilterWorking, matchChild: newMatchChild, matchSelf: newMatchSelf } = treeNode.filterReult;
this.setState({ filterWorking: newFilterWorking, matchChild: newMatchChild, matchSelf: newMatchSelf });
});
this.eventOffCallbacks.push(
doc?.onDropLocationChanged(() => {
this.setState({
@ -216,7 +226,7 @@ export default class TreeNodeView extends PureComponent<{
let shouldShowModalTreeNode: boolean = this.shouldShowModalTreeNode();
// filter 处理
const { filterWorking, matchChild, matchSelf } = treeNode.filterReult;
const { filterWorking, matchChild, matchSelf } = this.state;
if (!isRootNode && filterWorking && !matchChild && !matchSelf) {
// 条件过滤生效时,如果未命中本节点或子节点,则不展示该节点
// 根节点始终展示

View File

@ -29,9 +29,15 @@ export default class TreeTitle extends PureComponent<{
title: string;
condition?: boolean;
visible?: boolean;
filterWorking: boolean;
keywords: string;
matchSelf: boolean;
} = {
editing: false,
title: '',
filterWorking: false,
keywords: '',
matchSelf: false,
};
private lastInput?: HTMLInputElement;
@ -100,6 +106,10 @@ export default class TreeTitle extends PureComponent<{
visible: !hidden,
});
});
treeNode.onFilterResultChanged(() => {
const { filterWorking: newFilterWorking, keywords: newKeywords, matchSelf: newMatchSelf } = treeNode.filterReult;
this.setState({ filterWorking: newFilterWorking, keywords: newKeywords, matchSelf: newMatchSelf });
});
}
deleteClick = () => {
const { treeNode } = this.props;
@ -109,7 +119,7 @@ export default class TreeTitle extends PureComponent<{
render() {
const { treeNode, isModal } = this.props;
const { pluginContext } = treeNode;
const { editing } = this.state;
const { editing, filterWorking, matchSelf, keywords } = this.state;
const isCNode = !treeNode.isRoot();
const { node } = treeNode;
const { componentMeta } = node;
@ -125,11 +135,9 @@ export default class TreeTitle extends PureComponent<{
marginLeft: -indent,
};
}
const { filterWorking, matchSelf, keywords } = treeNode.filterReult;
const Extra = pluginContext.extraTitle;
const { intlNode, common, config } = pluginContext;
const Tip = common.editorCabin.Tip;
const Title = common.editorCabin.Title;
const { Tip, Title } = common.editorCabin;
const couldHide = availableActions.includes('hide');
const couldLock = availableActions.includes('lock');
const couldUnlock = availableActions.includes('unlock');
@ -253,7 +261,7 @@ class RenameBtn extends PureComponent<{
}> {
render() {
const { intl, common } = this.props.treeNode.pluginContext;
const Tip = common.editorCabin.Tip;
const { Tip } = common.editorCabin;
return (
<div
className="tree-node-rename-btn"
@ -274,7 +282,7 @@ class LockBtn extends PureComponent<{
render() {
const { treeNode, locked } = this.props;
const { intl, common } = this.props.treeNode.pluginContext;
const Tip = common.editorCabin.Tip;
const { Tip } = common.editorCabin;
return (
<div
className="tree-node-lock-btn"
@ -300,7 +308,7 @@ class HideBtn extends PureComponent<{
render() {
const { treeNode, hidden } = this.props;
const { intl, common } = treeNode.pluginContext;
const Tip = common.editorCabin.Tip;
const { Tip } = common.editorCabin;
return (
<div
className="tree-node-hide-btn"

View File

@ -1,49 +0,0 @@
# Rax Renderer
Rax 渲染模块。
## 安装
```
$ npm install @alilc/lowcode-rax-renderer --save
```
## 使用
```js
import { createElement, render } from 'rax';
import DriverUniversal from 'driver-universal';
import RaxRenderer from '@ali/lowcode-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(
<RaxRenderer
schema={schema}
components={components}
/>,
document.getElementById('root'), { driver: DriverUniversal }
);
```

View File

@ -1,11 +0,0 @@
{
"plugins": [
[
"build-plugin-rax-component",
{
"type": "rax",
"targets": ["web"]
}
]
]
}

View File

@ -1,35 +0,0 @@
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(<Engine schema={schema} components={components} />, document.getElementById('root'), {
driver: DriverUniversal,
});

View File

@ -1 +0,0 @@
App({});

View File

@ -1,6 +0,0 @@
{
"pages": ["pages/index"],
"window": {
"defaultTitle": "demo"
}
}

View File

@ -1 +0,0 @@
<my-component></my-component>

View File

@ -1,4 +0,0 @@
Page({
onLoad() {},
onShow() {},
});

View File

@ -1,6 +0,0 @@
{
"defaultTitle": "Miniapp Rax Text demo",
"usingComponents": {
"my-component": "../components/Target/index"
}
}

View File

@ -1 +0,0 @@
App({});

View File

@ -1,6 +0,0 @@
{
"pages": ["pages/index"],
"window": {
"title": "demo"
}
}

View File

@ -1,4 +0,0 @@
Page({
onLoad() {},
onShow() {},
});

View File

@ -1,6 +0,0 @@
{
"title": "Wechat MiniProgram Rax Text demo",
"usingComponents": {
"my-component": "../components/Target/index"
}
}

View File

@ -1 +0,0 @@
<my-component></my-component>

View File

@ -1,54 +0,0 @@
{
"name": "@alilc/lowcode-rax-renderer",
"version": "1.2.5",
"description": "Rax renderer for Ali lowCode engine",
"main": "lib/index.js",
"module": "es/index.js",
"miniappConfig": {
"main": "lib/miniapp/index",
"main:wechat": "lib/wechat-miniprogram/index"
},
"files": [
"dist",
"es",
"lib"
],
"keywords": [
"low-code",
"lowcode",
"Rax"
],
"engines": {
"npm": ">=3.0.0"
},
"peerDependencies": {
"prop-types": "^15.7.2",
"rax": "^1.1.0"
},
"scripts": {
"start": "build-scripts start",
"build": "build-scripts build"
},
"dependencies": {
"@alilc/lowcode-renderer-core": "1.2.5",
"@alilc/lowcode-utils": "1.2.5",
"rax-find-dom-node": "^1.0.1"
},
"devDependencies": {
"@alib/build-scripts": "^0.1.0",
"build-plugin-rax-component": "^0.2.11",
"driver-universal": "^3.1.3"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"repository": {
"type": "http",
"url": "https://github.com/alibaba/lowcode-engine/tree/main/packages/rax-renderer"
},
"license": "MIT",
"homepage": "https://github.com/alibaba/lowcode-engine/#readme",
"gitHead": "2669f179e6f899d395ce1942d0fe04f9c5ed48a6",
"bugs": "https://github.com/alibaba/lowcode-engine/issues"
}

View File

@ -1,82 +0,0 @@
// @ts-nocheck
import { Component, forwardRef } from 'rax';
import PropTypes from 'prop-types';
import { AppHelper } from '@alilc/lowcode-utils';
import { utils, contextFactory } from '@alilc/lowcode-renderer-core';
import componentRendererFactory from '../renderer/component';
import blockRendererFactory from '../renderer/block';
const { forEach, isFileSchema } = utils;
export default function compFactory(schema, components = {}, componentsMap = {}, config = {}) {
// 自定义组件需要有自己独立的appHelper
const appHelper = new AppHelper(config);
const CompRenderer = componentRendererFactory();
const BlockRenderer = blockRendererFactory();
const AppContext = contextFactory();
class LNCompView extends Component {
static displayName = 'LceCompFactory';
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?.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 (
<AppContext.Consumer>
{context => {
this.context = context;
return (
<CompRenderer
{...props}
__appHelper={appHelper}
__components={{ ...components, Component: CompRenderer, Block: BlockRenderer }}
__componentsMap={componentsMap}
/>
);
}}
</AppContext.Consumer>
);
}
}
const ResComp = forwardRef((props, ref) => <LNCompView {...props} forwardedRef={ref} />);
forEach(schema.static, (val, key) => {
ResComp[key] = val;
});
ResComp.version = config.version || '0.0.0';
return ResComp;
}

View File

@ -1,52 +0,0 @@
import { Component, PureComponent, createElement, createContext, forwardRef } from 'rax';
import findDOMNode from 'rax-find-dom-node';
import {
adapter,
addonRendererFactory,
tempRendererFactory,
rendererFactory,
} from '@alilc/lowcode-renderer-core';
import pageRendererFactory from './renderer/page';
import componentRendererFactory from './renderer/component';
import blockRendererFactory from './renderer/block';
import CompFactory from './hoc/compFactory';
adapter.setRuntime({
Component,
PureComponent,
createContext,
createElement,
forwardRef,
findDOMNode,
});
adapter.setRenderers({
PageRenderer: pageRendererFactory(),
ComponentRenderer: componentRendererFactory(),
BlockRenderer: blockRendererFactory(),
AddonRenderer: addonRendererFactory(),
TempRenderer: tempRendererFactory(),
});
function factory() {
const Renderer = rendererFactory();
return class extends Renderer {
constructor(props: any, context: any) {
super(props, context);
}
isValidComponent(obj: any) {
return obj?.prototype?.setState || obj?.prototype instanceof Component;
}
};
}
const RaxRenderer: any = factory();
const Engine: any = RaxRenderer;
export {
Engine,
CompFactory,
};
export default RaxRenderer;

View File

@ -1,25 +0,0 @@
import { blockRendererFactory, types } from '@alilc/lowcode-renderer-core';
const raxBlockRendererFactory: () => any = () => {
const OriginBlock = blockRendererFactory();
return class BlockRenderer extends OriginBlock {
render() {
// @ts-ignore
const that: types.IRenderer = this;
const { __schema, __components } = that.props;
if (that.__checkSchema(__schema)) {
return '区块 schema 结构异常!';
}
that.__debug(`render - ${__schema.fileName}`);
const children = ((context) => {
that.context = context;
that.__generateCtx({});
that.__render();
return that.__renderComp((__components as any)?.Block, { blockContext: that });
});
return that.__renderContextConsumer(children);
}
};
};
export default raxBlockRendererFactory;

View File

@ -1,37 +0,0 @@
import { componentRendererFactory, types } from '@alilc/lowcode-renderer-core';
const raxComponentRendererFactory: () => any = () => {
const OriginComponent = componentRendererFactory();
return class ComponentRenderer extends OriginComponent {
render() {
// @ts-ignore
const that: types.IRenderer = this;
const { __schema, __components } = that.props;
if (that.__checkSchema(__schema)) {
return '自定义组件 schema 结构异常!';
}
that.__debug(`render - ${__schema.fileName}`);
const { noContainer } = that.__parseData(__schema.props);
const children = ((context) => {
that.context = context;
that.__generateCtx({ component: that });
that.__render();
// 传 null使用内置的 div 来渲染,解决在页面中渲染 vc-component 报错的问题
return that.__renderComp(null, {
compContext: that,
blockContext: that,
});
});
const content = that.__renderContextConsumer(children);
if (noContainer) {
return content;
}
return that.__renderContent(content);
}
};
};
export default raxComponentRendererFactory;

View File

@ -1,38 +0,0 @@
import { pageRendererFactory, types } from '@alilc/lowcode-renderer-core';
const raxPageRendererFactory: () => any = () => {
const OriginPage = pageRendererFactory();
return class PageRenderer extends OriginPage {
async componentDidUpdate() {
// @ts-ignore
super.componentDidUpdate(...arguments);
}
render() {
// @ts-ignore
const that: types.IRenderer = this;
const { __schema, __components } = that.props;
if (that.__checkSchema(__schema)) {
return '页面 schema 结构异常!';
}
that.__debug(`render - ${__schema?.fileName}`);
const { Page } = __components as any;
if (Page) {
const children = ((context) => {
that.context = context;
that.__render();
return that.__renderComp(Page, { pageContext: that });
});
return that.__renderContextConsumer(children);
}
return that.__renderContent(that.__renderContextConsumer((context) => {
that.context = context;
return that.__renderContextProvider({ pageContext: that });
}));
}
};
};
export default raxPageRendererFactory;

View File

@ -1,26 +0,0 @@
{
"compilerOptions": {
"lib": ["es2015", "dom"],
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"strict": false,
"strictPropertyInitialization": false,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"jsx": "react",
"jsxFactory": "createElement",
"importHelpers": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"sourceMap": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"outDir": "lib"
},
"exclude": ["test", "lib", "es", "node_modules"],
"include": [
"src"
]
}

View File

@ -1,9 +0,0 @@
{
"plugins": [
["@babel/plugin-transform-react-jsx", {
"pragma": "createElement", // default pragma is React.createElement
"pragmaFrag": "createFragment", // default is React.Fragment
"throwIfNamespace": false // defaults to true
}]
]
}

View File

@ -1 +0,0 @@
module.exports = require('../../babel.config');

View File

@ -1,3 +0,0 @@
{
"plugins": ["@alilc/build-plugin-lce", "./build.plugin.js"]
}

View File

@ -1,5 +0,0 @@
module.exports = ({ onGetWebpackConfig }) => {
onGetWebpackConfig((config) => {
config.performance.hints(false);
});
};

View File

@ -1,38 +0,0 @@
{
"entry": {
"rax-simulator-renderer": "src/index"
},
"sourceMap": true,
"library": "___RaxSimulatorRenderer___",
"libraryTarget": "umd",
"externals": {
"react": "var window.React",
"react-dom": "var window.ReactDOM",
"prop-types": "var window.PropTypes",
"@alifd/next": "var Next",
"@alilc/lowcode-engine-ext": "var window.AliLowCodeEngineExt",
"rax": "var window.Rax",
"moment": "var moment",
"lodash": "var _"
},
"polyfill": false,
"outputDir": "dist",
"vendor": false,
"ignoreHtmlTemplate": true,
"plugins": [
"build-plugin-react-app",
[
"build-plugin-fusion",
{
"externalNext": "umd"
}
],
[
"build-plugin-moment-locales",
{
"locales": ["zh-cn"]
}
],
"./build.plugin.js"
]
}

View File

@ -1,55 +0,0 @@
{
"name": "@alilc/lowcode-rax-simulator-renderer",
"version": "1.2.5",
"description": "rax simulator renderer for alibaba lowcode designer",
"main": "lib/index.js",
"module": "es/index.js",
"license": "MIT",
"files": [
"dist"
],
"scripts": {
"build": "NODE_OPTIONS=--max_old_space_size=8192 build-scripts build",
"build:umd": "build-scripts build --config build.umd.json"
},
"dependencies": {
"@alilc/lowcode-designer": "1.2.5",
"@alilc/lowcode-rax-renderer": "1.2.5",
"@alilc/lowcode-types": "1.2.5",
"@alilc/lowcode-utils": "1.2.5",
"classnames": "^2.2.6",
"driver-universal": "^3.1.3",
"history": "^5.0.0",
"lodash": "^4.17.19",
"mobx": "^6.3.0",
"mobx-react": "^7.2.0",
"path-to-regexp": "3.2.0",
"rax-find-dom-node": "^1.0.0",
"react": "^16",
"react-dom": "^16.7.0"
},
"devDependencies": {
"@alib/build-scripts": "^0.1.18",
"@babel/plugin-transform-react-jsx": "^7.10.4",
"@types/classnames": "^2.2.7",
"@types/node": "^13.7.1",
"@types/rax": "^1.0.0",
"@types/react": "^16",
"@types/react-dom": "^16",
"build-plugin-rax-component": "^0.2.11"
},
"peerDependencies": {
"rax": "^1.1.0"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
},
"repository": {
"type": "http",
"url": "https://github.com/alibaba/lowcode-engine/tree/main/packages/rax-simulator-renderer"
},
"homepage": "https://github.com/alibaba/lowcode-engine/#readme",
"gitHead": "2669f179e6f899d395ce1942d0fe04f9c5ed48a6",
"bugs": "https://github.com/alibaba/lowcode-engine/issues"
}

View File

@ -1,34 +0,0 @@
import { Component } from 'rax';
import lg from '@ali/vu-logger';
import './index.less';
export class UnknownComponent extends Component {
props: {
_componentName: string;
};
render() {
lg.log('ERROR_NO_COMPONENT_VIEW');
lg.error('Error component information:', this.props);
return <div className="engine-unknow-component"> {this.props._componentName} </div>;
}
}
export class FaultComponent extends Component {
props: {
_componentName: string;
};
render() {
return <div className="engine-fault-component"> {this.props._componentName} </div>;
}
}
export class HiddenComponent extends Component {
render() {
return <div className="engine-hidden-component"></div>;
}
}
export default { FaultComponent, HiddenComponent, UnknownComponent };

View File

@ -1,251 +0,0 @@
import { Component } from 'rax';
class Leaf extends Component {
static displayName = 'Leaf';
static componentMetadata = {
componentName: 'Leaf',
configure: {
props: [{
name: 'children',
setter: 'StringSetter',
}],
// events/className/style/general/directives
supports: false,
},
};
render() {
const { children } = this.props;
return children;
}
}
export default Leaf;
// import { Component, createElement } from 'rax';
// import findDOMNode from 'rax-find-dom-node';
// import { each, get, omit } from 'lodash';
// import { getView, setNativeNode, createNodeStyleSheet } from '../renderUtils';
// import { FaultComponent, HiddenComponent, UnknownComponent } from '../UnusualComponent';
// export interface ILeaf {
// leaf: any;
// }
// export default class Leaf extends Component<ILeaf, {}> {
// static displayName = 'Leaf';
// state = {
// hasError: false,
// };
// willDetach: any[];
// styleSheet: any;
// context: any;
// refs: any;
// componentWillMount() {
// const { leaf } = this.props;
// this.willDetach = [
// leaf.onPropsChange(() => {
// // 强制刷新
// this.setState(this.state);
// }),
// leaf.onChildrenChange(() => {
// // 强制刷新
// this.setState(this.state);
// }),
// leaf.onStatusChange((status: { dropping: boolean }, field: string) => {
// // console.log({...status}, field)
// if (status.dropping !== false) {
// // 当 dropping 为 Insertion 对象时,强制渲染会出错,原因待查
// return;
// }
// if (field === 'dragging' || field === 'dropping' || field === 'pseudo' || field === 'visibility') {
// // 强制刷新
// this.setState(this.state);
// }
// }),
// ];
// /**
// * while props replaced
// * bind the new event on it
// */
// leaf.onPropsReplace(() => {
// this.willDetach[0]();
// this.willDetach[0] = leaf.onPropsChange(() => {
// // 强制刷新
// this.setState(this.state);
// });
// });
// }
// componentDidMount() {
// this.modifyDOM();
// }
// shouldComponentUpdate() {
// // forceUpdate 的替代方案
// return true;
// // const pageCanRefresh = this.leaf.getPage().canRefresh();
// // if (pageCanRefresh) {
// // return pageCanRefresh;
// // }
// // const getExtProps = obj => {
// // const { leaf, ...props } = obj;
// // return props;
// // };
// // return !shallowEqual(getExtProps(this.props), getExtProps(nextProps));
// }
// componentDidUpdate() {
// this.modifyDOM();
// }
// componentWillUnmount() {
// if (this.willDetach) {
// this.willDetach.forEach((off) => off());
// }
// setNativeNode(this.props.leaf, null);
// }
// componentDidCatch() {
// this.setState({ hasError: true }, () => {
// console.log('error');
// });
// }
// modifyDOM() {
// const shell = findDOMNode(this);
// const { leaf } = this.props;
// // 与 React 不同rax 的 findDOMNode 找不到节点时,
// // shell 会是 <!-- empty -->,而不是 null
// // 所以这里进行是否为注释的判断
// if (shell && shell.nodeType !== window.Node.COMMENT_NODE) {
// setNativeNode(leaf, shell);
// if (leaf.getStatus('dragging')) {
// get(shell, 'classList').add('engine-dragging');
// } else {
// get(shell, 'classList').remove('engine-dragging');
// }
// each(get(shell, 'classList'), (cls) => {
// if (cls.substring(0, 8) === '-pseudo-') {
// get(shell, 'classList').remove(cls);
// }
// });
// const pseudo = leaf.getStatus('pseudo');
// if (pseudo) {
// get(shell, 'classList').add(`-pseudo-${pseudo}`);
// }
// } else {
// setNativeNode(leaf, null);
// }
// }
// render() {
// const props = omit(this.props, ['leaf']);
// const { leaf } = this.props;
// const componentName = leaf.getComponentName();
// const View = getView(componentName);
// const newProps = {
// _componentName: componentName,
// };
// if (!View) {
// return createElement(UnknownComponent, {
// // _componentName: componentName,
// ...newProps,
// });
// }
// let staticProps = {
// ...leaf.getStaticProps(false),
// ...props,
// _componentName: componentName,
// _leaf: leaf,
// componentId: leaf.getId(),
// };
// if (!leaf.isVisibleInPane()) {
// return null;
// }
// if (!leaf.isVisible()) {
// return createElement(HiddenComponent, {
// ...staticProps,
// });
// }
// if (this.state.hasError) {
// return createElement(FaultComponent, {
// // _componentName: componentName,
// ...newProps,
// });
// }
// if (this.styleSheet) {
// this.styleSheet.parentNode.removeChild(this.styleSheet);
// }
// this.styleSheet = createNodeStyleSheet(staticProps);
// if (leaf.ableToModifyChildren()) {
// const children = leaf
// .getChildren()
// .filter((child: any) => child.getComponentName() !== 'Slot')
// .map((child: any) =>
// createElement(Leaf, {
// key: child.getId(),
// leaf: child,
// }),
// );
// // const insertion = leaf.getStatus('dropping');
// // InsertionGhost 都是React节点,用Rax渲染会报错后面这些节点需要通过Rax组件来实现
// // if (children.length < 1 && insertion && insertion.getIndex() !== null) {
// // //children = [];
// // children = [<InsertionGhost key="insertion" />];
// // } else if (insertion && insertion.isNearEdge()) {
// // if (insertion.isNearAfter()) {
// // children.push(<InsertionGhost key="insertion" />);
// // } else {
// // children.unshift(<InsertionGhost key="insertion" />);
// // }
// // }
// staticProps = {
// ...staticProps,
// ...this.processSlots(this.props.leaf.getChildren()),
// };
// return createElement(
// View,
// {
// ...staticProps,
// },
// children,
// );
// }
// return createElement(View, {
// ...staticProps,
// });
// }
// processSlots(children: Rax.RaxNodeArray) {
// const slots: any = {};
// children &&
// children.length &&
// children.forEach((child: any) => {
// if (child.getComponentName() === 'Slot') {
// slots[child.getPropValue('slotName')] = <Leaf key={child.getId()} leaf={child} />;
// }
// });
// return slots;
// }
// }

View File

@ -1,83 +0,0 @@
import { isObject } from 'lodash';
import { css } from '@alilc/lowcode-utils';
const { toCss } = css;
const engine = (window as any).VisualEngine;
const { Trunk, Viewport } = engine;
export const NativeNodeCache: any = {};
function ucfirst(s: string) {
return s.charAt(0).toUpperCase() + s.substring(1);
}
export function shallowEqual(obj: { [key: string]: string }, tObj: { [key: string]: string }) {
for (const i in obj) {
if (Object.prototype.hasOwnProperty.call(obj, i) && obj[i] !== tObj[i]) {
return false;
}
}
return true;
}
export function createNodeStyleSheet(props: any) {
if (props && props.fieldId) {
let styleProp = props.__style__;
if (isObject(styleProp)) {
styleProp = toCss(styleProp);
}
if (typeof styleProp === 'string') {
const s = document.createElement('style');
const cssId = `_style_pesudo_${ props.fieldId}`;
const cssClass = `_css_pesudo_${ props.fieldId}`;
props.className = cssClass;
s.setAttribute('type', 'text/css');
s.setAttribute('id', cssId);
document.getElementsByTagName('head')[0].appendChild(s);
s.appendChild(
document.createTextNode(
styleProp
.replace(/(\d+)rpx/g, (a, b) => {
return `${b / 2}px`;
})
.replace(/:root/g, `.${ cssClass}`),
),
);
return s;
}
}
}
export function setNativeNode(leaf: any, node: Rax.RaxNode) {
const id = leaf.getId();
if (NativeNodeCache[id] === node) {
return;
}
NativeNodeCache[id] = node;
leaf.mountChange();
}
export function getView(componentName: string) {
// let view = new Trunk().getPrototypeView(componentName);
let view = Trunk.getPrototypeView(componentName);
if (!view) {
return null;
}
const viewport = Viewport.getViewport();
if (viewport) {
const [mode, device] = viewport.split('-', 2).map(ucfirst);
if (view.hasOwnProperty(device)) {
view = view[device];
}
if (view.hasOwnProperty(mode)) {
view = view[mode];
}
}
return view;
}

View File

@ -1,54 +0,0 @@
import { Component } from 'rax';
class Slot extends Component {
static displayName = 'Slot';
static componentMetadata = {
componentName: 'Slot',
configure: {
props: [{
name: '___title',
title: {
type: 'i18n',
'en-US': 'Slot Title',
'zh-CN': '插槽标题',
},
setter: 'StringSetter',
defaultValue: '插槽容器',
}, {
name: '___params',
title: {
type: 'i18n',
'en-US': 'Slot Params',
'zh-CN': '插槽入参',
},
setter: {
componentName: 'ArraySetter',
props: {
itemSetter: {
componentName: 'StringSetter',
props: {
placeholder: {
type: 'i18n',
'zh-CN': '参数名称',
'en-US': 'Argument Name',
},
},
},
},
},
}],
// events/className/style/general/directives
supports: false,
},
};
render() {
const { children } = this.props;
return (
<div className="lc-container">{children}</div>
);
}
}
export default Slot;

View File

@ -1,4 +0,0 @@
// NOTE: 仅做类型标注,切勿做其它用途
import { BuiltinSimulatorHost } from '@alilc/lowcode-designer';
export const host: BuiltinSimulatorHost = (window as any).LCSimulatorHost;

View File

@ -1,2 +0,0 @@
declare module 'rax-find-dom-node';
declare module '@alilc/lowcode-rax-renderer/lib/index';

View File

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

View File

@ -1,288 +0,0 @@
// Inspired by react-router and universal-router
import { useState, useEffect, useLayoutEffect, createElement } from 'rax';
import pathToRegexp from 'path-to-regexp';
const cache = {};
function decodeParam(val) {
try {
return decodeURIComponent(val);
} catch (err) {
return val;
}
}
function matchPath(route, pathname, parentParams) {
let { path, routes, exact: end = true, strict = false, sensitive = false } = route;
// If not has path or has routes that should do not exact match
if (path == null || routes) {
end = false;
}
// Default path is empty
path = path || '';
const regexpCacheKey = `${path}|${end}|${strict}|${sensitive}`;
const keysCacheKey = `${regexpCacheKey }|`;
let regexp = cache[regexpCacheKey];
const keys = cache[keysCacheKey] || [];
if (!regexp) {
regexp = pathToRegexp(path, keys, {
end,
strict,
sensitive,
});
cache[regexpCacheKey] = regexp;
cache[keysCacheKey] = keys;
}
const result = regexp.exec(pathname);
if (!result) {
return null;
}
const url = result[0];
const params = { ...parentParams, history: router.history, location: router.history.location };
for (let i = 1; i < result.length; i++) {
const key = keys[i - 1];
const prop = key.name;
const value = result[i];
if (value !== undefined || !Object.prototype.hasOwnProperty.call(params, prop)) {
if (key.repeat) {
params[prop] = value ? value.split(key.delimiter).map(decodeParam) : [];
} else {
params[prop] = value ? decodeParam(value) : value;
}
}
}
return {
path: !end && url.charAt(url.length - 1) === '/' ? url.slice(1) : url,
params,
};
}
function matchRoute(route, baseUrl, pathname, parentParams) {
let matched;
let childMatches;
let childIndex = 0;
return {
next() {
if (!matched) {
matched = matchPath(route, pathname, parentParams);
if (matched) {
return {
done: false,
$: {
route,
baseUrl,
path: matched.path,
params: matched.params,
},
};
}
}
if (matched && route.routes) {
while (childIndex < route.routes.length) {
if (!childMatches) {
const childRoute = route.routes[childIndex];
childRoute.parent = route;
childMatches = matchRoute(
childRoute,
baseUrl + matched.path,
pathname.slice(matched.path.length),
matched.params,
);
}
const childMatch = childMatches.next();
if (!childMatch.done) {
return {
done: false,
$: childMatch.$,
};
}
childMatches = null;
childIndex++;
}
}
return { done: true };
},
};
}
let _initialized = false;
let _routerConfig = null;
const router = {
history: null,
handles: [],
errorHandler() { },
addHandle(handle) {
return router.handles.push(handle);
},
removeHandle(handleId) {
router.handles[handleId - 1] = null;
},
triggerHandles(component) {
router.handles.forEach((handle) => {
handle && handle(component);
});
},
match(fullpath) {
if (fullpath == null) return;
router.fullpath = fullpath;
const parent = router.root;
const matched = matchRoute(
parent,
parent.path,
fullpath,
);
function next(parent) {
const current = matched.next();
if (current.done) {
const error = new Error(`No match for ${fullpath}`);
return router.errorHandler(error, router.history.location);
}
let { component } = current.$.route;
if (typeof component === 'function') {
component = component(current.$.params, router.history.location);
}
if (component instanceof Promise) {
// Lazy loading component by import('./Foo')
return component.then((component) => {
// Check current fullpath avoid router has changed before lazy loading complete
if (fullpath === router.fullpath) {
router.triggerHandles(component);
}
});
} else if (component != null) {
router.triggerHandles(component);
return component;
} else {
return next(parent);
}
}
return next(parent);
},
};
function matchLocation({ pathname }) {
router.match(pathname);
}
function getInitialComponent(routerConfig) {
let InitialComponent = [];
if (_routerConfig === null) {
if (process.env.NODE_ENV !== 'production') {
if (!routerConfig) {
throw new Error('Error: useRouter should have routerConfig, see: https://www.npmjs.com/package/rax-use-router.');
}
if (!routerConfig.history || !routerConfig.routes) {
throw new Error('Error: routerConfig should contain history and routes, see: https://www.npmjs.com/package/rax-use-router.');
}
}
_routerConfig = routerConfig;
}
if (_routerConfig.InitialComponent) {
InitialComponent = _routerConfig.InitialComponent;
}
router.history = _routerConfig.history;
return InitialComponent;
}
let unlisten = null;
let handleId = null;
let pathes = '';
export function useRouter(routerConfig) {
const [component, setComponent] = useState(getInitialComponent(routerConfig));
let newPathes = '';
if (routerConfig) {
_routerConfig = routerConfig;
const { routes } = _routerConfig;
router.root = Array.isArray(routes) ? { routes } : routes;
if (Array.isArray(routes)) {
newPathes = routes.map(it => it.path).join(',');
} else {
newPathes = routes.path;
}
}
if (_initialized && _routerConfig.history) {
if (newPathes !== pathes) {
matchLocation(_routerConfig.history.location);
pathes = newPathes;
}
}
useLayoutEffect(() => {
if (unlisten) {
unlisten();
unlisten = null;
}
if (handleId) {
router.removeHandle(handleId);
handleId = null;
}
const { history } = _routerConfig;
const { routes } = _routerConfig;
router.root = Array.isArray(routes) ? { routes } : routes;
handleId = router.addHandle((component) => {
setComponent(component);
});
// Init path match
if (_initialized || !_routerConfig.InitialComponent) {
matchLocation(history.location);
pathes = newPathes;
}
unlisten = history.listen(({ location }) => {
matchLocation(location);
pathes = newPathes;
});
_initialized = true;
return () => {
pathes = '';
router.removeHandle(handleId);
handleId = null;
unlisten();
unlisten = null;
};
}, []);
return { component };
}
export function withRouter(Component) {
function Wrapper(props) {
const { history } = router;
return createElement(Component, { ...props, history, location: history.location });
}
Wrapper.displayName = `withRouter(${ Component.displayName || Component.name })`;
Wrapper.WrappedComponent = Component;
return Wrapper;
}

View File

@ -1,275 +0,0 @@
import RaxRenderer from '@alilc/lowcode-rax-renderer';
import { History } from 'history';
import { Component, createElement, Fragment } from 'rax';
import { useRouter } from './rax-use-router';
import { DocumentInstance, SimulatorRendererContainer } from './renderer';
import './renderer.less';
import { uniqueId } from '@alilc/lowcode-utils';
import { GlobalEvent } from '@alilc/lowcode-types';
import { host } from './host';
// patch cloneElement avoid lost keyProps
const originCloneElement = (window as any).Rax.cloneElement;
(window as any).Rax.cloneElement = (child: any, { _leaf, ...props }: any = {}, ...rest: any[]) => {
if (child.ref && props.ref) {
const dRef = props.ref;
const cRef = child.ref;
props.ref = (x: any) => {
if (cRef) {
if (typeof cRef === 'function') {
cRef(x);
} else {
try {
cRef.current = x;
} catch (e) {
console.error(e);
}
}
}
if (dRef) {
if (typeof dRef === 'function') {
dRef(x);
} else {
try {
dRef.current = x;
} catch (e) {
console.error(e);
}
}
}
};
}
return originCloneElement(child, props, ...rest);
};
export default class SimulatorRendererView extends Component<{ rendererContainer: SimulatorRendererContainer }> {
private unlisten: any;
componentDidMount() {
const { rendererContainer } = this.props;
this.unlisten = rendererContainer.onLayoutChange(() => {
this.forceUpdate();
});
}
componentWillUnmount() {
if (this.unlisten) {
this.unlisten();
}
}
render() {
const { rendererContainer } = this.props;
return (
<Layout rendererContainer={rendererContainer}>
<Routes rendererContainer={rendererContainer} history={rendererContainer.history} />
</Layout>
);
}
}
export const Routes = (props: {
rendererContainer: SimulatorRendererContainer;
history: History;
}) => {
const { rendererContainer, history } = props;
const { documentInstances } = rendererContainer;
const routes = {
history,
routes: documentInstances.map(instance => {
return {
path: instance.path,
component: (props: any) => <Renderer key={instance.id} rendererContainer={rendererContainer} documentInstance={instance} {...props} />,
};
}),
};
const { component } = useRouter(routes);
return component;
};
function ucfirst(s: string) {
return s.charAt(0).toUpperCase() + s.substring(1);
}
function getDeviceView(view: any, device: string, mode: string) {
if (!view || typeof view === 'string') {
return view;
}
// compatible vision Mobile | Preview
device = ucfirst(device);
if (device === 'Mobile' && view.hasOwnProperty(device)) {
view = view[device];
}
mode = ucfirst(mode);
if (mode === 'Preview' && view.hasOwnProperty(mode)) {
view = view[mode];
}
return view;
}
class Layout extends Component<{ rendererContainer: SimulatorRendererContainer }> {
constructor(props: any) {
super(props);
this.props.rendererContainer.onReRender(() => {
this.forceUpdate();
});
}
render() {
const { rendererContainer, children } = this.props;
const { layout } = rendererContainer;
if (layout) {
const { Component, props, componentName } = layout;
if (Component) {
return <Component props={props}>{children}</Component>;
}
if (componentName && rendererContainer.getComponent(componentName)) {
return createElement(
rendererContainer.getComponent(componentName),
{
...props,
rendererContainer,
},
[children],
);
}
}
return <Fragment>{children}</Fragment>;
}
}
class Renderer extends Component<{
rendererContainer: SimulatorRendererContainer;
documentInstance: DocumentInstance;
}> {
private unlisten: any;
private key: string;
private startTime: number | null = null;
componentWillMount() {
this.key = uniqueId('renderer');
}
componentDidMount() {
const { documentInstance } = this.props;
this.unlisten = documentInstance.onReRender((params) => {
if (params && params.shouldRemount) {
this.key = uniqueId('renderer');
}
this.forceUpdate();
});
}
componentWillUnmount() {
if (this.unlisten) {
this.unlisten();
}
}
shouldComponentUpdate() {
return false;
}
componentDidUpdate() {
if (this.startTime) {
const time = Date.now() - this.startTime;
const nodeCount = host.designer.currentDocument?.getNodeCount?.();
host.designer.editor?.eventBus.emit(GlobalEvent.Node.Rerender, {
componentName: 'Renderer',
type: 'All',
time,
nodeCount,
});
}
}
schemaChangedSymbol = false;
getSchemaChangedSymbol = () => {
return this.schemaChangedSymbol;
};
setSchemaChangedSymbol = (symbol: boolean) => {
this.schemaChangedSymbol = symbol;
};
render() {
const { documentInstance } = this.props;
const { container, document } = documentInstance;
const { designMode, device } = container;
const { rendererContainer: renderer } = this.props;
this.startTime = Date.now();
this.schemaChangedSymbol = false;
return (
<RaxRenderer
schema={documentInstance.schema}
components={renderer.components}
appHelper={renderer.context}
context={renderer.context}
device={device}
designMode={renderer.designMode}
key={this.key}
__host={host}
__container={container}
suspended={documentInstance.suspended}
self={documentInstance.scope}
onCompGetRef={(schema: any, ref: any) => {
documentInstance.mountInstance(schema.id, ref);
}}
thisRequiredInJSE={host.thisRequiredInJSE}
documentId={document.id}
getNode={(id: string) => documentInstance.getNode(id) as any}
rendererName="PageRenderer"
customCreateElement={(Component: any, props: any, children: any) => {
const { __id, ...viewProps } = props;
viewProps.componentId = __id;
const leaf = documentInstance.getNode(__id);
viewProps._leaf = leaf;
viewProps._componentName = leaf?.componentName;
// 如果是容器 && 无children && 高宽为空 增加一个占位容器,方便拖动
if (
!viewProps.dataSource &&
leaf?.isContainer() &&
(children == null || (Array.isArray(children) && !children.length)) &&
(!viewProps.style || Object.keys(viewProps.style).length === 0)
) {
children = (
<div className="lc-container-placeholder" style={viewProps.placeholderStyle}>
{viewProps.placeholder || '拖拽组件或模板到这里'}
</div>
);
}
// if (viewProps._componentName === 'Menu') {
// Object.assign(viewProps, {
// _componentName: 'Menu',
// className: '_css_pesudo_menu_kbrzyh0f',
// context: { VE: (window as any).VisualLowCodeRenderer },
// direction: undefined,
// events: { ignored: true },
// fieldId: 'menu_kbrzyh0f',
// footer: '',
// header: '',
// mode: 'inline',
// onItemClick: { ignored: true },
// onSelect: { ignored: true },
// popupAlign: 'follow',
// selectMode: false,
// triggerType: 'click',
// });
// console.info('menuprops', viewProps);
// }
return createElement(
getDeviceView(Component, device, designMode),
viewProps,
leaf?.isContainer() ? (children == null ? [] : Array.isArray(children) ? children : [children]) : children,
);
}}
/>
);
}
}

Some files were not shown because too many files have changed in this diff Show More