Compare commits

...

108 Commits

Author SHA1 Message Date
唐澜
f6305c2284
Merge pull request #3050 from alibaba/chore/update-codeowners
chore: update code owners
2024-06-11 21:12:45 +08:00
1ncounter
2892a01d29 chore: update code owners 2024-06-11 21:07:42 +08:00
GitHub Action
bd85ca3ca6 chore(release): publish 1.3.2 2024-02-23 01:51:23 +00:00
liujuping
44beb2a25a fix(workspace): fix workspace editorView is undefined 2024-02-07 13:43:39 +08:00
liujuping
86d50e0946 fix(setter): params is not shell SettingField 2024-02-04 16:42:46 +08:00
liujuping
19eb917259 refactor(plugin-command): add plugin-command package 2024-02-01 16:24:08 +08:00
GitHub Action
501ad872c4 chore(docs): publish documentation 2024-02-01 08:17:01 +00:00
liujuping
76b2a05049 docs(command): add command api docs 2024-02-01 16:15:16 +08:00
liujuping
5657d9c084 feat: update test package.yaml 2024-02-01 14:49:25 +08:00
liujuping
5b46d96df7 refactor(plugin-command): add plugin-command package 2024-02-01 14:45:41 +08:00
liujuping
24393211b4 refactor(plugin-command): add plugin-command package 2024-02-01 12:54:39 +08:00
liujuping
de95b87b1e feat: update ts defined 2024-02-01 07:01:05 +08:00
liujuping
43921cea2d fix(workspace): fix workspace.plugins.xxx api is invalid 2024-01-31 16:05:21 +08:00
liujuping
557a462b9f fix(prop): emit event when delete prop 2024-01-30 15:01:58 +08:00
liujuping
80bb7102b6 feat(command): update default commands 2024-01-30 14:38:11 +08:00
liujuping
ed7befbff0 feat(context-menu): prevent event bubbling when "menus" is empty 2024-01-30 11:05:08 +08:00
liujuping
e3a19896d7 fix(prop): emit event when unset prop 2024-01-30 11:04:54 +08:00
唐澜
dfe6878028
Merge pull request #2888 from Zeral-Zhang/develop
fix(react-simulator-renderer): detached node has children
2024-01-26 17:51:16 +08:00
liujuping
6e89d4d605 feat(setter): add field ts 2024-01-26 17:17:44 +08:00
GitHub Action
739572172a chore(docs): publish documentation 2024-01-26 08:40:59 +00:00
liujuping
b97570f10c docs(demo): add dialog use desc 2024-01-26 16:40:04 +08:00
ZeralZhang
b29c53901e fix(react-simulator-renderer): detached node has children
detached node has children will return false, causing memory leaks.
2024-01-26 09:38:37 +08:00
liujuping
b3880e9a96 feat(command): add command apis 2024-01-25 16:38:58 +08:00
liujuping
947c621822 test(utils): add more test case for checkPropTypes 2024-01-25 15:57:05 +08:00
liujuping
dca3448cbf chore(editor-core): init test 2024-01-25 15:56:41 +08:00
liujuping
0e65f02116 feat(utils): move checkPropTypes to utils module 2024-01-25 10:52:58 +08:00
liujuping
cd67a8cea3 test(designer): add parse-metadata test 2024-01-25 10:10:55 +08:00
liujuping
e6ba79b4d7 feat(utils): checkPropTypes suport IPublicTypePropType as rule for check 2024-01-25 10:10:38 +08:00
GitHub Action
de738b5d7c chore(docs): publish documentation 2024-01-18 01:06:41 +00:00
liujuping
9b19d8f889 docs: remove title level from 示例 sections in documentation 2024-01-18 09:05:49 +08:00
GitHub Action
4025a7d2e0 chore(release): publish 1.3.1 2024-01-17 06:01:02 +00:00
liujuping
8f8908c832 fix(context-menu): fix context menu component instance changed 2024-01-17 11:23:45 +08:00
liujuping
a8b9b2b5e1 chore: add "set -e" in build.sh 2024-01-16 18:23:29 +08:00
liujuping
84b4c76db2 chore: update build-plugin-lce version 2024-01-16 17:38:39 +08:00
liujuping
4dd6f7a352 feat: update comtext menu component 2024-01-16 10:40:05 +08:00
GitHub Action
7f2b2870aa chore(docs): publish documentation 2024-01-16 02:35:43 +00:00
liujuping
d8406c5bab docs: update resource docs 2024-01-16 10:34:50 +08:00
JackLian
f9fe38e65a Merge branch 'release/1.3.0' 2024-01-15 14:18:16 +08:00
GitHub Action
1943172d65 chore(release): publish 1.3.0 2024-01-15 06:12:28 +00:00
JackLian
d957be8072 chore: add pub:minor and pub:major commands 2024-01-15 14:03:23 +08:00
JackLian
e77063636c chore: add pub:minor and pub:major commands 2024-01-15 14:02:38 +08:00
eightHundreds
d1a8bacd75 fix(designer): node.ts cannot generate declaration file 2024-01-15 13:00:05 +08:00
liujuping
adb9f6b090 fix(canvas): clipboard init error 2024-01-15 10:11:09 +08:00
liujuping
1132a30483 feat: update context-menu message 2024-01-12 16:09:06 +08:00
liujuping
34a5a11b49 fix: fix clipboard.setData fial in workspace mode 2024-01-12 16:08:31 +08:00
GitHub Action
8931d8393d chore(docs): publish documentation 2024-01-12 01:17:52 +00:00
liujuping
844ca783d7 feat(context-menu): add context-menu css theme, help config, ts define 2024-01-12 09:16:53 +08:00
GitHub Action
6f9359e042 chore(docs): publish documentation 2024-01-11 10:13:55 +00:00
liujuping
ce72fc1b16 feat(common-ui): add HelpTip 2024-01-11 18:13:00 +08:00
GitHub Action
18642ee923 chore(docs): publish documentation 2024-01-11 03:17:25 +00:00
liujuping
bb5d7ddf82 feat(context-menu): update context-menu docs, details, styles 2024-01-11 11:16:31 +08:00
GitHub Action
3e7d199a7c chore(docs): publish documentation 2024-01-11 02:50:04 +00:00
liujuping
a7d3996fa2 feat(common): add common.utils.intl API 2024-01-11 10:49:12 +08:00
GitHub Action
a00c5c9b60 chore(docs): publish documentation 2024-01-10 07:21:10 +00:00
liujuping
3627ae326a feat: optimize context menu details 2024-01-10 15:20:16 +08:00
GitHub Action
14728476b6 chore(docs): publish documentation 2024-01-09 08:43:50 +00:00
liujuping
c381b85f0a fix(context-menu): fix context menu bugs 2024-01-09 16:43:01 +08:00
liujuping
8f0291fc3e fix(context-menu): fix context menu bugs 2024-01-09 09:08:40 +08:00
liujuping
6ded1a6384 chore: update CODEOWNERS 2024-01-09 09:07:59 +08:00
GitHub Action
6ce381f60a chore(docs): publish documentation 2024-01-08 07:06:58 +00:00
JackLian
6288ab9d60 fix(docs): fix invalid doc content 2024-01-08 15:06:00 +08:00
GitHub Action
3f369e446d chore(docs): publish documentation 2024-01-08 06:56:19 +00:00
liujuping
1b00c61a32 feat(engine): add context menu 2024-01-08 14:55:28 +08:00
GitHub Action
d47c2d2f91 chore(docs): publish documentation 2024-01-05 08:09:04 +00:00
liujuping
31a031ce4e feat: removed Rax packages, discontinuing support for Rax 2024-01-05 16:08:04 +08:00
Arun
ba53d6c690 ci: Use GITHUB_OUTPUT envvar instead of set-output command 2024-01-05 11:51:45 +08:00
GitHub Action
e8aebb9aa3 chore(docs): publish documentation 2024-01-05 03:44:36 +00:00
liujuping
be0456fb39 feat: add commonUI API 2024-01-05 11:42:36 +08:00
liujuping
d81eb8d75d style(outline): update selected item style 2024-01-05 11:42:13 +08:00
liujuping
c24c3d8ae5 feat: add hideComponentAction config 2024-01-05 11:41:46 +08:00
LiuTeiTei
e1f3a11c41 fix: trigger onFilterResultChanged when filtered 2024-01-05 11:10:08 +08:00
wangwei
173978ffd4 chore(release): code-gen 1.1.7 2024-01-03 15:17:40 +08:00
聪小陈
7b85a35b24
feat: zip publisher support browser (#2744) 2024-01-02 18:15:30 +08:00
GitHub Action
b2abb67440 chore(release): publish 1.2.5 2023-12-28 06:22:34 +00:00
JackLian
0d6b2b8bf8 Merge branch 'main' into develop 2023-12-28 14:04:50 +08:00
JackLian
8a1345527f Merge branch 'release/1.2.4' 2023-12-28 14:04:04 +08:00
GitHub Action
89e912ba1a chore(release): publish 1.2.4 2023-12-28 05:57:44 +00:00
liujuping
f7ba053476 fix(outline-pane): hover invalid 2023-12-26 15:09:36 +08:00
eightHundreds
f483970aef fix: trigger node.add event on mergeChildren 2023-12-26 14:51:03 +08:00
liujuping
73dacadaee style(skeleton): update setting pane styles 2023-12-26 09:50:00 +08:00
GitHub Action
733229985b chore(docs): publish documentation 2023-12-19 09:57:55 +00:00
liujuping
e79f68611a feat(skeleton): add onDisableWidget & onEnableWidget APIs 2023-12-19 17:56:18 +08:00
GitHub Action
4a502f823c chore(docs): publish documentation 2023-12-19 01:32:47 +00:00
liujuping
2bf3aa70a7 docs: add skeleton.getAreaItems docs 2023-12-19 09:31:52 +08:00
liujuping
594abc4e6c style(designer): update bem-tools index 2023-12-18 14:34:53 +08:00
liujuping
16713f4b84 feat(skeleton): Add TS defs for modules & optimize Tabs display with array contents 2023-12-18 10:03:41 +08:00
liujuping
711a5f66b9 style: remove .lc-workbench-center z-index 2023-12-18 10:02:26 +08:00
GitHub Action
c6e0025256 chore(docs): publish documentation 2023-12-14 09:28:13 +00:00
JackLian
230e63ce55 docs: add new link to video page 2023-12-14 17:27:07 +08:00
JackLian
39d030bfc3 chore(workflows/publish-docs): update config 2023-12-14 14:51:03 +08:00
JackLian
9394aecc7c chore(workflows/publish-docs): update config 2023-12-14 14:49:25 +08:00
JackLian
53078ba5f0 chore(workflows/publish-docs): update config 2023-12-14 14:46:17 +08:00
JackLian
5d08b6858d chore(workflows/publish-docs): update config 2023-12-14 14:45:01 +08:00
JackLian
729083aa01 chore(workflows/publish-docs): update config 2023-12-14 14:18:57 +08:00
GitHub Action
729b123e46 chore(docs): publish documentation 2023-12-14 06:16:05 +00:00
JackLian
b56a2a9274 chore(workflows/publish-docs): update config 2023-12-14 14:13:58 +08:00
GitHub Action
cdcc8c8a90 chore(docs): publish documentation 2023-12-14 06:04:49 +00:00
JackLian
2349599daa Merge branch 'main' into develop 2023-12-14 14:01:13 +08:00
JackLian
613142b2d8 chore(workflows/publish-docs): add workflow_dispatch trigger 2023-12-14 14:00:52 +08:00
JackLian
08401c76f9 chore(workflows/publish-docs): add workflow_dispatch trigger 2023-12-14 13:57:29 +08:00
liujuping
ec843543f0 chore: update docs publish action 2023-12-14 13:54:02 +08:00
GitHub Action
ef573d3ad9 chore(docs): publish documentation 2023-12-14 03:09:41 +00:00
JackLian
a567f12473 docs: add new link to video page 2023-12-14 11:08:47 +08:00
JackLian
bb50a20ef1 chore(docs): use tnpm as oss-syncing source 2023-12-14 10:44:34 +08:00
JackLian
9d526ebcd8 chore(workflows/publish-docs): update commit message 2023-12-14 10:22:26 +08:00
JackLian
27bf7babe5 chore: use tnpm as oss-syncing source 2023-12-14 10:17:13 +08:00
GitHub Action
c0ddba5543 Update package version 2023-12-14 02:15:38 +00:00
刘菊萍(絮黎)
f2d8fca188 docs: update workspace.md 2023-12-14 10:14:56 +08:00
225 changed files with 4466 additions and 3273 deletions

4
.github/CODEOWNERS vendored
View File

@ -2,7 +2,7 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence
* @liujuping @JackLian
* @liujuping @1ncounter
/modules/material-parser @akirakai
/modules/code-generator @leoyuan
/modules/code-generator @qingniaotonghua

34
.github/workflows/pre build.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: Pre Build
on:
push:
paths:
- 'packages/**'
- '!packages/**.md'
pull_request:
paths:
- 'packages/**'
- '!packages/**.md'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install dependencies and setup
run: npm install && npm run setup
- name: Build
run: npm run build
- name: Check build status
run: |
if [ $? -eq 0 ]; then
echo "Build succeeded!"
else
echo "Build failed!"
exit 1
fi

View File

@ -6,6 +6,7 @@ on:
- develop
paths:
- 'docs/docs/**'
workflow_dispatch:
jobs:
publish-docs:
@ -15,7 +16,8 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
ref: 'develop'
node-version: '16'
registry-url: 'https://registry.npmjs.org'
- run: cd docs && npm install
- run: |
@ -24,14 +26,14 @@ jobs:
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add package.json
git commit -m "Update package version"
git commit -m "chore(docs): publish documentation"
git push
- run: cd docs && npm publish
- run: cd docs && npm run build && npm publish
env:
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
@ -48,4 +50,4 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
body: '🚀 New version has been released: ' + '${{ needs.publish-docs.outputs.version }}'
})
})

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

@ -2,13 +2,17 @@ name: Publish Engine
on:
workflow_dispatch:
inputs:
publishCommand:
description: 'publish command'
required: true
jobs:
publish-engine:
runs-on: ubuntu-latest
if: >-
contains(github.ref, 'refs/heads/release/') &&
(github.actor == 'JackLian' || github.actor == 'liujuping')
(github.actor == '1ncounter' || github.actor == 'liujuping')
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
@ -21,9 +25,9 @@ jobs:
npm run build
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
- run: npm run pub
- run: npm run ${{ github.event.inputs.publishCommand }}
env:
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

@ -105,4 +105,36 @@ jobs:
run: npm i && npm run setup:skip-build
- name: test
run: cd packages/utils && npm test
run: cd packages/utils && npm test
test-editor-core:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
- name: install
run: npm i && npm run setup:skip-build
- name: test
run: cd packages/editor-core && npm test
test-plugin-command:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
- name: install
run: npm i && npm run setup:skip-build
- name: test
run: cd packages/plugin-command && npm test

2
.gitignore vendored
View File

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

View File

@ -1,6 +1,6 @@
---
title: canvas - 画布 API
sidebar_position: 12
sidebar_position: 10
---
> **@types** [IPublicApiCanvas](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/api/canvas.ts)<br/>

101
docs/docs/api/command.md Normal file
View File

@ -0,0 +1,101 @@
---
title: command - 指令 API
sidebar_position: 10
---
## 模块概览
该模块使得与命令系统的交互成为可能,提供了一种全面的方式来处理、执行和管理应用程序中的命令。
## 接口
### IPublicApiCommand
与命令交互的接口。它提供了注册、注销、执行和管理命令的方法。
## 方法
### registerCommand
注册一个新命令及其处理函数。
```
typescriptCopy code
/**
* 注册一个新的命令及其处理程序。
* @param command {IPublicTypeCommand} - 要注册的命令。
*/
registerCommand(command: IPublicTypeCommand): void;
```
### unregisterCommand
注销一个已存在的命令。
```
typescriptCopy code
/**
* 注销一个已存在的命令。
* @param name {string} - 要注销的命令的名称。
*/
unregisterCommand(name: string): void;
```
### executeCommand
根据名称和提供的参数执行命令,确保参数符合命令的定义。
```
typescriptCopy code
/**
* 根据名称和提供的参数执行命令。
* @param name {string} - 要执行的命令的名称。
* @param args {IPublicTypeCommandHandlerArgs} - 命令的参数。
*/
executeCommand(name: string, args?: IPublicTypeCommandHandlerArgs): void;
```
### batchExecuteCommand
批量执行命令,在所有命令执行后进行重绘,历史记录中只记录一次。
```
typescriptCopy code
/**
* 批量执行命令,随后进行重绘,历史记录中只记录一次。
* @param commands {Array} - 命令对象的数组,包含名称和可选参数。
*/
batchExecuteCommand(commands: { name: string; args?: IPublicTypeCommandHandlerArgs }[]): void;
```
### listCommands
列出所有已注册的命令。
```
typescriptCopy code
/**
* 列出所有已注册的命令。
* @returns {IPublicTypeListCommand[]} - 已注册命令的数组。
*/
listCommands(): IPublicTypeListCommand[];
```
### onCommandError
为命令执行过程中的错误注册错误处理回调函数。
```
typescriptCopy code
/**
* 为命令执行过程中的错误注册一个回调函数。
* @param callback {(name: string, error: Error) => void} - 错误处理的回调函数。
*/
onCommandError(callback: (name: string, error: Error) => void): void;
```

View File

@ -1,6 +1,6 @@
---
title: common - 通用 API
sidebar_position: 11
sidebar_position: 10
---
> **@types** [IPublicApiCommon](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/api/common.ts)<br/>
@ -82,7 +82,7 @@ executeTransaction(fn: () => void, type: IPublicEnumTransitionType): void;
```
**@since v1.0.16**
##### 示例
**示例**
```typescript
import { common } from '@alilc/lowcode-engine';
import { IPublicEnumTransitionType } from '@alilc/lowcode-types';
@ -132,7 +132,8 @@ createIntl(instance: string | object): {
**@since v1.0.17**
##### 示例
**示例**
```typescript
import { common } from '@alilc/lowcode-engine';
import enUS from './en-US.json';
@ -145,6 +146,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: 10
---
## 简介
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

@ -1,6 +1,6 @@
---
title: config - 配置 API
sidebar_position: 8
sidebar_position: 5
---
> **@types** [IPublicModelEngineConfig](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/engine-config.ts)<br/>
@ -24,7 +24,7 @@ sidebar_position: 8
*/
get(key: string, defaultValue?: any): any;
```
#### 示例
**示例**
```typescript
import { config } from '@alilc/lowcode-engine';
@ -43,7 +43,7 @@ config.get('keyB', { a: 1 });
*/
set(key: string, value: any): void;
```
#### 示例
**示例**
```typescript
import { config } from '@alilc/lowcode-engine';
@ -63,7 +63,7 @@ config.set('keyC', 1);
has(key: string): boolean;
```
#### 示例
**示例**
```typescript
import { config } from '@alilc/lowcode-engine';
@ -81,7 +81,7 @@ config.has('keyD');
*/
setConfig(config: { [key: string]: any }): void;
```
#### 示例
**示例**
```typescript
import { config } from '@alilc/lowcode-engine';
@ -134,7 +134,7 @@ config.getPreference().set(`${panelName}-pinned-status-isFloat`, false, 'skeleto
*/
onceGot(key: string): Promise<any>;
```
#### 示例
**示例**
```typescript
import { config } from '@alilc/lowcode-engine';
@ -160,7 +160,7 @@ const value = await config.onceGot('keyA');
*/
onGot(key: string, fn: (data: any) => void): () => void;
```
#### 示例
**示例**
```typescript
import { config } from '@alilc/lowcode-engine';

View File

@ -1,6 +1,6 @@
---
title: config options - 配置列表
sidebar_position: 13
sidebar_position: 5
---
> **@types** [IPublicTypeEngineOptions](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/engine-options.ts)<br/>
@ -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

@ -1,6 +1,6 @@
---
title: event - 事件 API
sidebar_position: 7
sidebar_position: 10
---
> **@types** [IPublicApiEvent](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/api/event.ts)<br/>

View File

@ -1,6 +1,6 @@
---
title: hotkey - 快捷键 API
sidebar_position: 5
sidebar_position: 10
---
> **@types** [IPublicApiHotkey](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/api/hotkey.ts)<br/>

View File

@ -1,6 +1,6 @@
---
title: init - 初始化 API
sidebar_position: 10
sidebar_position: 0
---
> **@since** v1.0.0

View File

@ -1,6 +1,6 @@
---
title: logger - 日志 API
sidebar_position: 9
sidebar_position: 10
---
> **@types** [IPublicApiLogger](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/api/logger.ts)<br/>

View File

@ -1,6 +1,6 @@
---
title: material - 物料 API
sidebar_position: 2
sidebar_position: 10
---
> **@types** [IPublicApiMaterial](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/api/material.ts)<br/>
@ -39,7 +39,7 @@ setAssets(assets: IPublicTypeAssetsJson): void;
相关类型:[IPublicTypeAssetsJson](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/assets-json.ts)
##### 示例
**示例**
直接在项目中引用 npm 包
```javascript
import { material } from '@alilc/lowcode-engine';
@ -85,7 +85,7 @@ getAssets(): IPublicTypeAssetsJson;
相关类型:[IPublicTypeAssetsJson](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/assets-json.ts)
##### 示例
**示例**
```typescript
import { material } from '@alilc/lowcode-engine';
@ -106,7 +106,7 @@ loadIncrementalAssets(incrementalAssets: IPublicTypeAssetsJson): Promise<void>;
```
相关类型:[IPublicTypeAssetsJson](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/assets-json.ts)
##### 示例
**示例**
```typescript
import { material } from '@alilc/lowcode-engine';
import assets1 from '@alilc/mc-assets-<siteId>/assets.json';
@ -146,7 +146,7 @@ addBuiltinComponentAction(action: IPublicTypeComponentAction): void;
相关类型:[IPublicTypeComponentAction](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/component-action.ts)
##### 示例
**示例**
新增设计扩展位,并绑定事件
```typescript
import { material } from '@alilc/lowcode-engine';
@ -186,7 +186,7 @@ removeBuiltinComponentAction(name: string): void;
- lock锁定不可编辑
- unlock解锁可编辑
##### 示例
**示例**
```typescript
import { material } from '@alilc/lowcode-engine';
@ -222,7 +222,7 @@ modifyBuiltinComponentAction(
##### 示例
**示例**
给原始的 remove 扩展时间添加执行前后的日志
```typescript
import { material } from '@alilc/lowcode-engine';
@ -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
获取指定名称的物料元数据
@ -252,7 +335,7 @@ getComponentMeta(componentName: string): IPublicModelComponentMeta | null;
```
相关类型:[IPublicModelComponentMeta](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/component-meta.ts)
##### 示例
**示例**
```typescript
import { material } from '@alilc/lowcode-engine';
@ -273,7 +356,7 @@ material.getComponentMeta('Input');
```
相关类型:[IPublicModelComponentMeta](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/component-meta.ts)
##### 示例
**示例**
```typescript
import { material } from '@alilc/lowcode-engine';
@ -310,7 +393,7 @@ registerMetadataTransducer(
): void;
```
##### 示例
**示例**
给每一个组件的配置添加高级配置面板,其中有一个是否渲染配置项
```typescript
import { material } from '@alilc/lowcode-engine'
@ -392,7 +475,7 @@ material.registerMetadataTransducer((transducer) => {
getRegisteredMetadataTransducers(): IPublicTypeMetadataTransducer[];
```
##### 示例
**示例**
```typescript
import { material } from '@alilc/lowcode-engine'
@ -413,7 +496,7 @@ onChangeAssets(fn: () => void): IPublicTypeDisposable;
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
##### 示例
**示例**
```typescript
import { material } from '@alilc/lowcode-engine';

View File

@ -1,6 +1,6 @@
{
"label": "模型定义 Models",
"position": 14,
"position": 100,
"collapsed": false,
"collapsible": true
}

View File

@ -15,6 +15,12 @@ sidebar_position: 13
`@type {string}`
### id
资源 id
`@type {string}`
### name
资源名字
@ -44,3 +50,9 @@ sidebar_position: 13
资源配置信息
`@type {Object}`
### config
资源配置信息
`@type {Object}`

View File

@ -1,6 +1,6 @@
---
title: plugins - 插件 API
sidebar_position: 4
sidebar_position: 2
---
> **@types** [IPublicApiPlugins](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/api/plugins.ts)<br/>
> **@since** v1.0.0

View File

@ -1,6 +1,6 @@
---
title: project - 模型 API
sidebar_position: 3
sidebar_position: 10
---
## 模块简介
@ -201,7 +201,7 @@ addPropsTransducer(
- [IPublicTypePropsTransducer](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/props-transducer.ts)
- [IPublicEnumTransformStage](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/enum/transform-stage.ts)
#### 示例
**示例**
在保存的时候删除每一个组件的 props.hidden
```typescript
import { project } from '@alilc/lowcode-engine';

View File

@ -1,6 +1,6 @@
---
title: setters - 设置器 API
sidebar_position: 6
sidebar_position: 10
---
> **@types** [IPublicApiSetters](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/api/setters.ts)<br/>
> **@since** v1.0.0

View File

@ -1,6 +1,6 @@
---
title: simulatorHost - 模拟器 API
sidebar_position: 3
sidebar_position: 10
---
> **@types** [IPublicApiSimulatorHost](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/api/simulator-host.ts)<br/>
> **@since** v1.0.0
@ -20,7 +20,7 @@ sidebar_position: 3
*/
set(key: string, value: any): void;
```
#### 示例
**示例**
设置若干用于画布渲染的变量比如画布大小、locale 等。
以设置画布大小为例:

View File

@ -1,6 +1,6 @@
---
title: skeleton - 面板 API
sidebar_position: 1
sidebar_position: 10
---
> **@types** [IPublicApiSkeleton](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/api/skeleton.ts)<br/>
> **@since** v1.0.0
@ -297,7 +297,24 @@ showArea(areaName: string): void;
hideArea(areaName: string): void;
```
### getAreaItems
获取某个区域下的所有面板实例
```typescript
/**
* 获取某个区域下的所有面板实例
* @param areaName IPublicTypeWidgetConfigArea
*/
getAreaItems(areaName: IPublicTypeWidgetConfigArea): IPublicModelSkeletonItem[] | undefined;
```
相关类型:[IPublicModelSkeletonItem](https://github.com/alibaba/lowcode-engine/blob/main/packages/shell/src/model/skeleton-item.ts)
### registerConfigTransducer
注册一个面板的配置转换器transducer
```typescript
@ -370,7 +387,7 @@ export default controlPanelWidthPlugin;
* @param listener
* @returns
*/
onShowPanel(listener: (...args: any[]) => void): IPublicTypeDisposable;
onShowPanel(listener: (paneName?: string, panel?: IPublicModelSkeletonItem) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
@ -386,11 +403,38 @@ onShowPanel(listener: (...args: any[]) => void): IPublicTypeDisposable;
* @param listener
* @returns
*/
onHidePanel(listener: (...args: any[]) => void): IPublicTypeDisposable;
onHidePanel(listener: (paneName?: string, panel?: IPublicModelSkeletonItem) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
### onDisableWidget
监听 Widget 实例 Disable 事件
```typescript
/**
* 监听 Widget 实例 Disable 事件
* @param listener
*/
onDisableWidget(listener: (paneName?: string, panel?: IPublicModelSkeletonItem) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
### onEnableWidget
监听 Widget 实例 Enable 事件
```typescript
/**
* 监听 Widget 实例 Enable 事件
* @param listener
*/
onEnableWidget(listener: (paneName?: string, panel?: IPublicModelSkeletonItem) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
### onShowWidget
@ -403,7 +447,7 @@ onHidePanel(listener: (...args: any[]) => void): IPublicTypeDisposable;
* @param listener
* @returns
*/
onShowWidget(listener: (...args: any[]) => void): IPublicTypeDisposable;
onShowWidget(listener: (paneName?: string, panel?: IPublicModelSkeletonItem) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
@ -419,7 +463,7 @@ onShowWidget(listener: (...args: any[]) => void): IPublicTypeDisposable;
* @param listener
* @returns
*/
onHideWidget(listener: (...args: any[]) => void): IPublicTypeDisposable;
onHideWidget(listener: (paneName?: string, panel?: IPublicModelSkeletonItem) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)

View File

@ -1,6 +1,6 @@
---
title: workspace - 应用级 API
sidebar_position: 12
sidebar_position: 10
---
> **[@experimental](./#experimental)**<br/>
@ -87,7 +87,7 @@ registerResourceType(resourceTypeModel: IPublicTypeResourceType): void;
setResourceList(resourceList: IPublicResourceList) {}
```
相关类型:[IPublicResourceOptions](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/resource-options.ts)
相关类型:[IPublicResourceData](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/resource-list.ts)
### openEditorWindow

View File

@ -2,6 +2,8 @@
title: 3. 如何通过按钮展示/隐藏弹窗
sidebar_position: 1
---
> 说明:这个方式依赖低代码弹窗组件是否对外保留了相关的 API不同的物料支持的方式不一样这里只针对综合场景的弹窗物料。
## 1.拖拽一个按钮
![image.png](https://img.alicdn.com/imgextra/i1/O1CN01kLaWA31D6WwTui9VW_!!6000000000167-2-tps-3584-1812.png)

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

@ -3,6 +3,8 @@
- [2023/08/03 初识低代码引擎](https://www.bilibili.com/video/BV1gu411p7TC)
# 社区视频
- [低代码从入门到实战:低代码引擎实践](https://www.bilibili.com/video/BV1aP4y1Q7Xa/)
- [低代码技术在研发团队的应用模式](https://www.bilibili.com/video/BV1L14y1Y72J/)
- [阿里低代码引擎项目实战 (1)-引擎 demo 部署到 faas 服务](https://www.bilibili.com/video/BV1B44y1P7GM/)
- [【有翻车】阿里低代码引擎项目实战 (2)-保存页面到远端存储](https://www.bilibili.com/video/BV1AS4y1K7DP/)
- [阿里巴巴低代码引擎项目实战 (3)-自定义组件接入](https://www.bilibili.com/video/BV1dZ4y1m76S/)

View File

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

View File

@ -19,7 +19,7 @@ const onResponse = function (res) {
chunks.push(chunk);
});
res.on('end', (chunk) => {
res.on('end', () => {
const body = Buffer.concat(chunks);
console.table(JSON.stringify(JSON.parse(body.toString()), null, 2));
});
@ -39,9 +39,9 @@ const postData = JSON.stringify({
},
],
// 可以发布指定源的 npm 包,默认公网 npm
useTnpm: false,
useTnpm: true,
});
req.write(postData);
req.end();
req.end();

View File

@ -1,6 +1,6 @@
{
"lerna": "4.0.0",
"version": "1.2.3",
"version": "1.3.2",
"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

@ -21,6 +21,8 @@
"lint:modules": "f2elint scan -q -i ./modules/*/src",
"lint:modules:fix": "f2elint fix -i ./modules/*/src",
"pub": "npm run watchdog:build && lerna publish patch --yes --force-publish --exact --no-changelog",
"pub:minor": "npm run watchdog:build && lerna publish minor --yes --force-publish --exact --no-changelog",
"pub:major": "npm run watchdog:build && lerna publish major --yes --force-publish --exact --no-changelog",
"pub:premajor": "npm run watchdog:build && lerna publish premajor --force-publish --exact --dist-tag beta --preid beta --no-changelog",
"pub:preminor": "npm run watchdog:build && lerna publish preminor --force-publish --exact --dist-tag beta --preid beta --no-changelog",
"pub:prepatch": "npm run watchdog:build && lerna publish prepatch --force-publish --exact --dist-tag beta --preid beta --no-changelog",
@ -52,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.5",
"babel-jest": "^26.5.2",
"@alilc/lowcode-test-mate": "^1.0.1"
},

View File

@ -21,6 +21,7 @@ const jestConfig = {
// testMatch: ['**/builtin-hotkey.test.ts'],
// testMatch: ['**/selection.test.ts'],
// testMatch: ['**/plugin/sequencify.test.ts'],
// testMatch: ['**/builtin-simulator/utils/parse-metadata.test.ts'],
transformIgnorePatterns: [
`/node_modules/(?!${esModules})/`,
],

View File

@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-designer",
"version": "1.2.3",
"version": "1.3.2",
"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.3",
"@alilc/lowcode-types": "1.2.3",
"@alilc/lowcode-utils": "1.2.3",
"@alilc/lowcode-editor-core": "1.3.2",
"@alilc/lowcode-types": "1.3.2",
"@alilc/lowcode-utils": "1.3.2",
"classnames": "^2.2.6",
"react": "^16",
"react-dom": "^16.7.0",

View File

@ -6,5 +6,5 @@
bottom: 0;
right: 0;
overflow: visible;
z-index: 800;
z-index: 1;
}

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

@ -16,8 +16,16 @@ export const primitiveTypes = [
'any',
];
interface LowcodeCheckType {
// isRequired, props, propName, componentName, location, propFullName, secret
(props: any, propName: string, componentName: string, ...rest: any[]): Error | null;
// (...reset: any[]): Error | null;
isRequired?: LowcodeCheckType;
type?: string | object;
}
// eslint-disable-next-line @typescript-eslint/ban-types
function makeRequired(propType: any, lowcodeType: string | object) {
function makeRequired(propType: any, lowcodeType: string | object): LowcodeCheckType {
function lowcodeCheckTypeIsRequired(...rest: any[]) {
return propType.isRequired(...rest);
}
@ -34,7 +42,7 @@ function makeRequired(propType: any, lowcodeType: string | object) {
}
// eslint-disable-next-line @typescript-eslint/ban-types
function define(propType: any = PropTypes.any, lowcodeType: string | object = {}) {
function define(propType: any = PropTypes.any, lowcodeType: string | object = {}): LowcodeCheckType {
if (!propType._inner && propType.name !== 'lowcodeCheckType') {
propType.lowcodeType = lowcodeType;
}

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

@ -70,7 +70,7 @@ export class Transducer {
}
if (isDynamicSetter(setter) && isDynamic) {
try {
setter = setter.call(context, context);
setter = setter.call(context.internalToShellField(), context.internalToShellField());
} catch (e) { console.error(e); }
}

View File

@ -462,6 +462,9 @@ export class NodeChildren implements INodeChildren {
const node: INode = this.owner.document?.createNode(child);
this.children.push(node);
node.internalSetParent(this.owner);
/* istanbul ignore next */
const editor = node.document?.designer.editor;
editor?.eventBus.emit('node.add', { node });
});
changed = true;
}

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

@ -353,7 +353,6 @@ export class Prop implements IProp, IPropParent {
@action
setValue(val: IPublicTypeCompositeValue) {
if (val === this._value) return;
const editor = this.owner.document?.designer.editor;
const oldValue = this._value;
this._value = val;
this._code = null;
@ -386,22 +385,31 @@ export class Prop implements IProp, IPropParent {
this.setupItems();
if (oldValue !== this._value) {
const propsInfo = {
key: this.key,
prop: this,
oldValue,
newValue: this._value,
};
editor?.eventBus.emit(GlobalEvent.Node.Prop.InnerChange, {
node: this.owner as any,
...propsInfo,
});
this.owner?.emitPropChange?.(propsInfo);
this.emitChange({ oldValue });
}
}
emitChange = ({
oldValue,
}: {
oldValue: IPublicTypeCompositeValue | UNSET;
}) => {
const editor = this.owner.document?.designer.editor;
const propsInfo = {
key: this.key,
prop: this,
oldValue,
newValue: this.type === 'unset' ? undefined : this._value,
};
editor?.eventBus.emit(GlobalEvent.Node.Prop.InnerChange, {
node: this.owner as any,
...propsInfo,
});
this.owner?.emitPropChange?.(propsInfo);
};
getValue(): IPublicTypeCompositeValue {
return this.export(IPublicEnumTransformStage.Serilize);
}
@ -462,7 +470,12 @@ export class Prop implements IProp, IPropParent {
*/
@action
unset() {
this._type = 'unset';
if (this._type !== 'unset') {
this._type = 'unset';
this.emitChange({
oldValue: this._value,
});
}
}
/**
@ -557,6 +570,7 @@ export class Prop implements IProp, IPropParent {
@action
remove() {
this.parent.delete(this);
this.unset();
}
/**

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,8 @@ import {
IPublicTypePluginRegisterOptions,
IPublicModelWindow,
IPublicEnumPluginRegisterLevel,
IPublicApiCommonUI,
IPublicApiCommand,
} from '@alilc/lowcode-types';
import PluginContext from './plugin-context';
@ -61,6 +63,8 @@ export interface ILowCodePluginContextPrivate {
set editorWindow(window: IPublicModelWindow);
set registerLevel(level: IPublicEnumPluginRegisterLevel);
set isPluginRegisteredInWorkspace(flag: boolean);
set commonUI(commonUI: IPublicApiCommonUI);
set command(command: IPublicApiCommand);
}
export interface ILowCodePluginContextApiAssembler {
assembleApis(

View File

@ -1,5 +1,7 @@
import '../../fixtures/window';
import { parseMetadata } from '../../../src/builtin-simulator/utils/parse-metadata';
import PropTypes from 'prop-types';
import { LowcodeTypes, parseMetadata, parseProps } from '../../../src/builtin-simulator/utils/parse-metadata';
import { default as ReactPropTypesSecret } from 'prop-types/lib/ReactPropTypesSecret';
describe('parseMetadata', () => {
it('parseMetadata', async () => {
@ -11,3 +13,165 @@ describe('parseMetadata', () => {
expect(result).toBeDefined();
});
});
describe('LowcodeTypes basic type validators', () => {
it('should validate string types', () => {
const stringValidator = LowcodeTypes.string;
// 对 stringValidator 进行测试
const props = { testProp: 'This is a string' };
const propName = 'testProp';
const componentName = 'TestComponent';
const result = stringValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeNull(); // No error for valid string
});
it('should fail with a non-string type', () => {
const stringValidator = LowcodeTypes.string;
const props = { testProp: 42 };
const propName = 'testProp';
const componentName = 'TestComponent';
const result = stringValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeInstanceOf(Error); // Error for non-string type
expect(result.message).toContain('Invalid prop `testProp` of type `number` supplied to `TestComponent`, expected `string`.');
});
it('should pass with a valid number', () => {
const numberValidator = LowcodeTypes.number;
const props = { testProp: 42 };
const propName = 'testProp';
const componentName = 'TestComponent';
const result = numberValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeNull(); // No error for valid number
});
it('should fail with a non-number type', () => {
const numberValidator = LowcodeTypes.number;
const props = { testProp: 'Not a number' };
const propName = 'testProp';
const componentName = 'TestComponent';
const result = numberValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeInstanceOf(Error); // Error for non-number type
expect(result.message).toContain('Invalid prop `testProp` of type `string` supplied to `TestComponent`, expected `number`.');
});
});
describe('Custom type constructors', () => {
it('should create a custom type validator using define', () => {
const customType = LowcodeTypes.define(PropTypes.string, 'customType');
const props = { testProp: 'This is a string' };
const propName = 'testProp';
const componentName = 'TestComponent';
// 测试有效值
const validResult = customType(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(validResult).toBeNull(); // No error for valid string
// 测试无效值
const invalidProps = { testProp: 42 };
const invalidResult = customType(invalidProps, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(invalidResult).toBeInstanceOf(Error); // Error for non-string type
// 验证 lowcodeType 属性
expect(customType.lowcodeType).toEqual('customType');
// 验证 isRequired 属性
const requiredResult = customType.isRequired(invalidProps, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(requiredResult).toBeInstanceOf(Error); // Error for non-string type
});
});
describe('Advanced type constructors', () => {
describe('oneOf Type Validator', () => {
const oneOfValidator = LowcodeTypes.oneOf(['red', 'green', 'blue']);
const propName = 'color';
const componentName = 'ColorPicker';
it('should pass with a valid value', () => {
const props = { color: 'red' };
const result = oneOfValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeNull(); // No error for valid value
});
it('should fail with an invalid value', () => {
const props = { color: 'yellow' };
const result = oneOfValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeInstanceOf(Error); // Error for invalid value
expect(result.message).toContain(`Invalid prop \`${propName}\` of value \`yellow\` supplied to \`${componentName}\`, expected one of ["red","green","blue"].`);
});
it('should fail with a non-existing value', () => {
const props = { color: 'others' };
const result = oneOfValidator(props, propName, componentName, 'prop', null, ReactPropTypesSecret);
expect(result).toBeInstanceOf(Error); // Error for non-existing value
expect(result.message).toContain(`Invalid prop \`${propName}\` of value \`others\` supplied to \`${componentName}\`, expected one of ["red","green","blue"].`);
});
});
});
describe('parseProps function', () => {
it('should correctly parse propTypes and defaultProps', () => {
const component = {
propTypes: {
name: LowcodeTypes.string,
age: LowcodeTypes.number,
},
defaultProps: {
name: 'John Doe',
age: 30,
},
};
const parsedProps = parseProps(component);
// 测试结果长度
expect(parsedProps.length).toBe(2);
// 测试 name 属性
const nameProp: any = parsedProps.find(prop => prop.name === 'name');
expect(nameProp).toBeDefined();
expect(nameProp.propType).toEqual('string');
expect(nameProp.defaultValue).toEqual('John Doe');
// 测试 age 属性
const ageProp: any = parsedProps.find(prop => prop.name === 'age');
expect(ageProp).toBeDefined();
expect(ageProp.propType).toEqual('number');
expect(ageProp.defaultValue).toEqual(30);
});
});
describe('parseProps function', () => {
it('should correctly parse propTypes and defaultProps', () => {
const component = {
propTypes: {
name: LowcodeTypes.string,
age: LowcodeTypes.number,
},
defaultProps: {
name: 'John Doe',
age: 30,
},
};
const parsedProps = parseProps(component);
// 测试结果长度
expect(parsedProps.length).toBe(2);
// 测试 name 属性
const nameProp: any = parsedProps.find(prop => prop.name === 'name');
expect(nameProp).toBeDefined();
expect(nameProp.propType).toEqual('string');
expect(nameProp.defaultValue).toEqual('John Doe');
// 测试 age 属性
const ageProp: any = parsedProps.find(prop => prop.name === 'age');
expect(ageProp).toBeDefined();
expect(ageProp.propType).toEqual('number');
expect(ageProp.defaultValue).toEqual(30);
});
});

View File

@ -3,7 +3,7 @@ import { Editor, engineConfig } from '@alilc/lowcode-editor-core';
import { Designer } from '../../../../src/designer/designer';
import { DocumentModel } from '../../../../src/document/document-model';
import { Prop, isProp, isValidArrayIndex } from '../../../../src/document/node/props/prop';
import { IPublicEnumTransformStage } from '@alilc/lowcode-types';
import { GlobalEvent, IPublicEnumTransformStage } from '@alilc/lowcode-types';
import { shellModelFactory } from '../../../../../engine/src/modules/shell-model-factory';
const slotNodeImportMockFn = jest.fn();
@ -24,14 +24,24 @@ const mockOwner = {
remove: slotNodeRemoveMockFn,
};
},
designer: {},
designer: {
editor: {
eventBus: {
emit: jest.fn(),
},
},
},
},
isInited: true,
emitPropChange: jest.fn(),
delete() {},
};
const mockPropsInst = {
owner: mockOwner,
delete() {},
};
mockPropsInst.props = mockPropsInst;
describe('Prop 类测试', () => {
@ -564,3 +574,124 @@ describe('其他导出函数', () => {
expect(isValidArrayIndex('2', 1)).toBeFalsy();
});
});
describe('setValue with event', () => {
let propInstance;
let mockEmitChange;
let mockEventBusEmit;
let mockEmitPropChange;
beforeEach(() => {
// Initialize the instance of your class
propInstance = new Prop(mockPropsInst, true, 'stringProp');;
// Mock necessary methods and properties
mockEmitChange = jest.spyOn(propInstance, 'emitChange');
propInstance.owner = {
document: {
designer: {
editor: {
eventBus: {
emit: jest.fn(),
},
},
},
},
emitPropChange: jest.fn(),
delete() {},
};
mockEventBusEmit = jest.spyOn(propInstance.owner.document.designer.editor.eventBus, 'emit');
mockEmitPropChange = jest.spyOn(propInstance.owner, 'emitPropChange');
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should correctly handle string values and emit changes', () => {
const oldValue = propInstance._value;
const newValue = 'new string value';
propInstance.setValue(newValue);
const expectedPartialPropsInfo = expect.objectContaining({
key: propInstance.key,
newValue, // You can specifically test only certain keys
oldValue,
});
expect(propInstance.getValue()).toBe(newValue);
expect(propInstance.type).toBe('literal');
expect(mockEmitChange).toHaveBeenCalledWith({ oldValue });
expect(mockEventBusEmit).toHaveBeenCalledWith(GlobalEvent.Node.Prop.InnerChange, expectedPartialPropsInfo);
expect(mockEmitPropChange).toHaveBeenCalledWith(expectedPartialPropsInfo);
});
it('should handle object values and set type to map', () => {
const oldValue = propInstance._value;
const newValue = 234;
const expectedPartialPropsInfo = expect.objectContaining({
key: propInstance.key,
newValue, // You can specifically test only certain keys
oldValue,
});
propInstance.setValue(newValue);
expect(propInstance.getValue()).toEqual(newValue);
expect(propInstance.type).toBe('literal');
expect(mockEmitChange).toHaveBeenCalledWith({ oldValue });
expect(mockEventBusEmit).toHaveBeenCalledWith(GlobalEvent.Node.Prop.InnerChange, expectedPartialPropsInfo);
expect(mockEmitPropChange).toHaveBeenCalledWith(expectedPartialPropsInfo);
});
it('should has event when unset call', () => {
const oldValue = propInstance._value;
propInstance.unset();
const expectedPartialPropsInfo = expect.objectContaining({
key: propInstance.key,
newValue: undefined, // You can specifically test only certain keys
oldValue,
});
expect(propInstance.getValue()).toEqual(undefined);
expect(propInstance.type).toBe('unset');
expect(mockEmitChange).toHaveBeenCalledWith({
oldValue,
newValue: undefined,
});
expect(mockEventBusEmit).toHaveBeenCalledWith(GlobalEvent.Node.Prop.InnerChange, expectedPartialPropsInfo);
expect(mockEmitPropChange).toHaveBeenCalledWith(expectedPartialPropsInfo);
propInstance.unset();
expect(mockEmitChange).toHaveBeenCalledTimes(1);
});
// remove
it('should has event when remove call', () => {
const oldValue = propInstance._value;
propInstance.remove();
const expectedPartialPropsInfo = expect.objectContaining({
key: propInstance.key,
newValue: undefined, // You can specifically test only certain keys
oldValue,
});
expect(propInstance.getValue()).toEqual(undefined);
// expect(propInstance.type).toBe('unset');
expect(mockEmitChange).toHaveBeenCalledWith({
oldValue,
newValue: undefined,
});
expect(mockEventBusEmit).toHaveBeenCalledWith(GlobalEvent.Node.Prop.InnerChange, expectedPartialPropsInfo);
expect(mockEmitPropChange).toHaveBeenCalledWith(expectedPartialPropsInfo);
propInstance.remove();
expect(mockEmitChange).toHaveBeenCalledTimes(1);
});
});

View File

@ -0,0 +1,9 @@
{
"plugins": [
"@alilc/build-plugin-lce",
"@alilc/lowcode-test-mate/plugin/index.ts"
],
"babelPlugins": [
["@babel/plugin-proposal-private-property-in-object", { "loose": true }]
]
}

View File

@ -0,0 +1,26 @@
const fs = require('fs');
const { join } = require('path');
const esModules = [].join('|');
const pkgNames = fs.readdirSync(join('..')).filter(pkgName => !pkgName.startsWith('.'));
const jestConfig = {
transformIgnorePatterns: [
`/node_modules/(?!${esModules})/`,
],
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
collectCoverage: false,
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/icons/**',
'!src/locale/**',
'!**/node_modules/**',
'!**/vendor/**',
],
};
// 只对本仓库内的 pkg 做 mapping
jestConfig.moduleNameMapper = {};
jestConfig.moduleNameMapper[`^@alilc/lowcode\\-(${pkgNames.join('|')})$`] = '<rootDir>/../$1/src';
module.exports = jestConfig;

View File

@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-editor-core",
"version": "1.2.3",
"version": "1.3.2",
"description": "Core Api for Ali lowCode engine",
"license": "MIT",
"main": "lib/index.js",
@ -10,12 +10,14 @@
"es"
],
"scripts": {
"build": "build-scripts build"
"build": "build-scripts build",
"test": "build-scripts test --config build.test.json",
"test:cov": "build-scripts test --config build.test.json --jest-coverage"
},
"dependencies": {
"@alifd/next": "^1.19.16",
"@alilc/lowcode-types": "1.2.3",
"@alilc/lowcode-utils": "1.2.3",
"@alilc/lowcode-types": "1.3.2",
"@alilc/lowcode-utils": "1.3.2",
"classnames": "^2.2.6",
"debug": "^4.1.1",
"intl-messageformat": "^9.3.1",

View File

@ -0,0 +1,91 @@
import { IPublicApiCommand, IPublicEnumTransitionType, IPublicModelPluginContext, IPublicTypeCommand, IPublicTypeCommandHandlerArgs, IPublicTypeListCommand } from '@alilc/lowcode-types';
import { checkPropTypes } from '@alilc/lowcode-utils';
export interface ICommand extends Omit<IPublicApiCommand, 'registerCommand' | 'batchExecuteCommand'> {
registerCommand(command: IPublicTypeCommand, options?: {
commandScope?: string;
}): void;
batchExecuteCommand(commands: { name: string; args: IPublicTypeCommandHandlerArgs }[], pluginContext?: IPublicModelPluginContext): void;
}
export interface ICommandOptions {
commandScope?: string;
}
export class Command implements ICommand {
private commands: Map<string, IPublicTypeCommand> = new Map();
private commandErrors: Function[] = [];
registerCommand(command: IPublicTypeCommand, options?: ICommandOptions): void {
if (!options?.commandScope) {
throw new Error('plugin meta.commandScope is required.');
}
const name = `${options.commandScope}:${command.name}`;
if (this.commands.has(name)) {
throw new Error(`Command '${command.name}' is already registered.`);
}
this.commands.set(name, {
...command,
name,
});
}
unregisterCommand(name: string): void {
if (!this.commands.has(name)) {
throw new Error(`Command '${name}' is not registered.`);
}
this.commands.delete(name);
}
executeCommand(name: string, args: IPublicTypeCommandHandlerArgs): void {
const command = this.commands.get(name);
if (!command) {
throw new Error(`Command '${name}' is not registered.`);
}
command.parameters?.forEach(d => {
if (!checkPropTypes(args[d.name], d.name, d.propType, 'command')) {
throw new Error(`Command '${name}' arguments ${d.name} is invalid.`);
}
});
try {
command.handler(args);
} catch (error) {
if (this.commandErrors && this.commandErrors.length) {
this.commandErrors.forEach(callback => callback(name, error));
} else {
throw error;
}
}
}
batchExecuteCommand(commands: { name: string; args: IPublicTypeCommandHandlerArgs }[], pluginContext: IPublicModelPluginContext): void {
if (!commands || !commands.length) {
return;
}
pluginContext.common.utils.executeTransaction(() => {
commands.forEach(command => this.executeCommand(command.name, command.args));
}, IPublicEnumTransitionType.REPAINT);
}
listCommands(): IPublicTypeListCommand[] {
return Array.from(this.commands.values()).map(d => {
const result: IPublicTypeListCommand = {
name: d.name,
};
if (d.description) {
result.description = d.description;
}
if (d.parameters) {
result.parameters = d.parameters;
}
return result;
});
}
onCommandError(callback: (name: string, error: Error) => void): void {
this.commandErrors.push(callback);
}
}

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

@ -1,5 +1,5 @@
import { ReactNode } from 'react';
import { IPublicApiSetters, IPublicTypeCustomView, IPublicTypeRegisteredSetter } from '@alilc/lowcode-types';
import { IPublicApiSetters, IPublicModelSettingField, IPublicTypeCustomView, IPublicTypeRegisteredSetter } from '@alilc/lowcode-types';
import { createContent, isCustomView } from '@alilc/lowcode-utils';
const settersMap = new Map<string, IPublicTypeRegisteredSetter & {
@ -28,7 +28,7 @@ export function registerSetter(
if (!setter.initialValue) {
const initial = getInitialFromSetter(setter.component);
if (initial) {
setter.initialValue = (field: any) => {
setter.initialValue = (field: IPublicModelSettingField) => {
return initial.call(field, field.getValue());
};
}
@ -81,7 +81,7 @@ export class Setters implements ISetters {
if (!setter.initialValue) {
const initial = getInitialFromSetter(setter.component);
if (initial) {
setter.initialValue = (field: any) => {
setter.initialValue = (field: IPublicModelSettingField) => {
return initial.call(field, field.getValue());
};
}

View File

@ -6,3 +6,4 @@ export * from './hotkey';
export * from './widgets';
export * from './config';
export * from './event-bus';
export * from './command';

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

@ -0,0 +1,326 @@
import { Command } from '../src/command';
describe('Command', () => {
let commandInstance;
let mockHandler;
beforeEach(() => {
commandInstance = new Command();
mockHandler = jest.fn();
});
describe('registerCommand', () => {
it('should register a command successfully', () => {
const command = {
name: 'testCommand',
handler: mockHandler,
};
commandInstance.registerCommand(command, { commandScope: 'testScope' });
const registeredCommand = commandInstance.listCommands().find(c => c.name === 'testScope:testCommand');
expect(registeredCommand).toBeDefined();
expect(registeredCommand.name).toBe('testScope:testCommand');
});
it('should throw an error if commandScope is not provided', () => {
const command = {
name: 'testCommand',
handler: mockHandler,
};
expect(() => {
commandInstance.registerCommand(command);
}).toThrow('plugin meta.commandScope is required.');
});
it('should throw an error if command is already registered', () => {
const command = {
name: 'testCommand',
handler: mockHandler,
};
commandInstance.registerCommand(command, { commandScope: 'testScope' });
expect(() => {
commandInstance.registerCommand(command, { commandScope: 'testScope' });
}).toThrow(`Command 'testCommand' is already registered.`);
});
});
afterEach(() => {
jest.clearAllMocks();
});
});
describe('unregisterCommand', () => {
let commandInstance;
let mockHandler;
beforeEach(() => {
commandInstance = new Command();
mockHandler = jest.fn();
// 先注册一个命令以便之后注销
const command = {
name: 'testCommand',
handler: mockHandler,
};
commandInstance.registerCommand(command, { commandScope: 'testScope' });
});
it('should unregister a command successfully', () => {
const commandName = 'testScope:testCommand';
expect(commandInstance.listCommands().find(c => c.name === commandName)).toBeDefined();
commandInstance.unregisterCommand(commandName);
expect(commandInstance.listCommands().find(c => c.name === commandName)).toBeUndefined();
});
it('should throw an error if the command is not registered', () => {
const nonExistingCommandName = 'testScope:nonExistingCommand';
expect(() => {
commandInstance.unregisterCommand(nonExistingCommandName);
}).toThrow(`Command '${nonExistingCommandName}' is not registered.`);
});
afterEach(() => {
jest.clearAllMocks();
});
});
describe('executeCommand', () => {
let commandInstance;
let mockHandler;
beforeEach(() => {
commandInstance = new Command();
mockHandler = jest.fn();
// 注册一个带参数校验的命令
const command = {
name: 'testCommand',
handler: mockHandler,
parameters: [
{ name: 'param1', propType: 'string' },
{ name: 'param2', propType: 'number' }
],
};
commandInstance.registerCommand(command, { commandScope: 'testScope' });
});
it('should execute a command successfully', () => {
const commandName = 'testScope:testCommand';
const args = { param1: 'test', param2: 42 };
commandInstance.executeCommand(commandName, args);
expect(mockHandler).toHaveBeenCalledWith(args);
});
it('should throw an error if the command is not registered', () => {
const nonExistingCommandName = 'testScope:nonExistingCommand';
expect(() => {
commandInstance.executeCommand(nonExistingCommandName, {});
}).toThrow(`Command '${nonExistingCommandName}' is not registered.`);
});
it('should throw an error if arguments are invalid', () => {
const commandName = 'testScope:testCommand';
const invalidArgs = { param1: 'test', param2: 'not-a-number' }; // param2 should be a number
expect(() => {
commandInstance.executeCommand(commandName, invalidArgs);
}).toThrow(`Command '${commandName}' arguments param2 is invalid.`);
});
it('should handle errors thrown by the command handler', () => {
const commandName = 'testScope:testCommand';
const args = { param1: 'test', param2: 42 };
const errorMessage = 'Command handler error';
mockHandler.mockImplementation(() => {
throw new Error(errorMessage);
});
expect(() => {
commandInstance.executeCommand(commandName, args);
}).toThrow(errorMessage);
});
afterEach(() => {
jest.clearAllMocks();
});
});
describe('batchExecuteCommand', () => {
let commandInstance;
let mockHandler;
let mockExecuteTransaction;
let mockPluginContext;
beforeEach(() => {
commandInstance = new Command();
mockHandler = jest.fn();
mockExecuteTransaction = jest.fn(callback => callback());
mockPluginContext = {
common: {
utils: {
executeTransaction: mockExecuteTransaction
}
}
};
// 注册几个命令
const command1 = {
name: 'testCommand1',
handler: mockHandler,
};
const command2 = {
name: 'testCommand2',
handler: mockHandler,
};
commandInstance.registerCommand(command1, { commandScope: 'testScope' });
commandInstance.registerCommand(command2, { commandScope: 'testScope' });
});
it('should execute a batch of commands', () => {
const commands = [
{ name: 'testScope:testCommand1', args: { param: 'value1' } },
{ name: 'testScope:testCommand2', args: { param: 'value2' } },
];
commandInstance.batchExecuteCommand(commands, mockPluginContext);
expect(mockExecuteTransaction).toHaveBeenCalledTimes(1);
expect(mockHandler).toHaveBeenCalledWith({ param: 'value1' });
expect(mockHandler).toHaveBeenCalledWith({ param: 'value2' });
});
it('should not execute anything if commands array is empty', () => {
commandInstance.batchExecuteCommand([], mockPluginContext);
expect(mockExecuteTransaction).not.toHaveBeenCalled();
expect(mockHandler).not.toHaveBeenCalled();
});
it('should handle errors thrown during command execution', () => {
const errorMessage = 'Command handler error';
mockHandler.mockImplementation(() => {
throw new Error(errorMessage);
});
const commands = [
{ name: 'testScope:testCommand1', args: { param: 'value1' } },
{ name: 'testScope:testCommand2', args: { param: 'value2' } },
];
expect(() => {
commandInstance.batchExecuteCommand(commands, mockPluginContext);
}).toThrow(errorMessage);
expect(mockExecuteTransaction).toHaveBeenCalledTimes(1); // Still called once
});
afterEach(() => {
jest.clearAllMocks();
});
});
describe('listCommands', () => {
let commandInstance;
let mockHandler;
beforeEach(() => {
commandInstance = new Command();
mockHandler = jest.fn();
});
it('should list all registered commands', () => {
// 注册几个命令
const command1 = {
name: 'testCommand1',
handler: mockHandler,
description: 'Test Command 1',
parameters: [{ name: 'param1', propType: 'string' }]
};
const command2 = {
name: 'testCommand2',
handler: mockHandler,
description: 'Test Command 2',
parameters: [{ name: 'param2', propType: 'number' }]
};
commandInstance.registerCommand(command1, { commandScope: 'testScope' });
commandInstance.registerCommand(command2, { commandScope: 'testScope' });
const listedCommands = commandInstance.listCommands();
expect(listedCommands.length).toBe(2);
expect(listedCommands).toEqual(expect.arrayContaining([
expect.objectContaining({
name: 'testScope:testCommand1',
description: 'Test Command 1',
parameters: [{ name: 'param1', propType: 'string' }]
}),
expect.objectContaining({
name: 'testScope:testCommand2',
description: 'Test Command 2',
parameters: [{ name: 'param2', propType: 'number' }]
})
]));
});
it('should return an empty array if no commands are registered', () => {
const listedCommands = commandInstance.listCommands();
expect(listedCommands).toEqual([]);
});
afterEach(() => {
jest.clearAllMocks();
});
});
describe('onCommandError', () => {
let commandInstance;
let mockHandler;
let mockErrorHandler1;
let mockErrorHandler2;
beforeEach(() => {
commandInstance = new Command();
mockHandler = jest.fn();
mockErrorHandler1 = jest.fn();
mockErrorHandler2 = jest.fn();
// 注册一个命令,该命令会抛出错误
const command = {
name: 'testCommand',
handler: () => {
throw new Error('Command execution failed');
},
};
commandInstance.registerCommand(command, { commandScope: 'testScope' });
});
it('should call all registered error handlers when a command throws an error', () => {
const commandName = 'testScope:testCommand';
commandInstance.onCommandError(mockErrorHandler1);
commandInstance.onCommandError(mockErrorHandler2);
expect(() => {
commandInstance.executeCommand(commandName, {});
}).not.toThrow();
// 确保所有错误处理函数都被调用,并且传递了正确的参数
expect(mockErrorHandler1).toHaveBeenCalledWith(commandName, expect.any(Error));
expect(mockErrorHandler2).toHaveBeenCalledWith(commandName, expect.any(Error));
});
it('should throw the error if no error handlers are registered', () => {
const commandName = 'testScope:testCommand';
expect(() => {
commandInstance.executeCommand(commandName, {});
}).toThrow('Command execution failed');
});
afterEach(() => {
jest.clearAllMocks();
});
});

View File

@ -1,6 +1,6 @@
{
"name": "@alilc/lowcode-editor-skeleton",
"version": "1.2.3",
"version": "1.3.2",
"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.3",
"@alilc/lowcode-editor-core": "1.2.3",
"@alilc/lowcode-types": "1.2.3",
"@alilc/lowcode-utils": "1.2.3",
"@alilc/lowcode-designer": "1.3.2",
"@alilc/lowcode-editor-core": "1.3.2",
"@alilc/lowcode-types": "1.3.2",
"@alilc/lowcode-utils": "1.3.2",
"classnames": "^2.2.6",
"react": "^16.8.1",
"react-dom": "^16.8.1"

View File

@ -225,7 +225,7 @@ class SettingFieldView extends Component<SettingFieldViewProps, SettingFieldView
const value = this.value;
let _onChange = extraProps?.onChange;
let onChangeAPI = extraProps?.onChange;
let stageName = this.stageName;
return createField(
@ -261,7 +261,7 @@ class SettingFieldView extends Component<SettingFieldViewProps, SettingFieldView
value,
});
field.setValue(value, true);
if (_onChange) _onChange(value, field);
if (onChangeAPI) onChangeAPI(value, field.internalToShellField());
},
onInitial: () => {
if (initialValue == null) {

View File

@ -65,8 +65,10 @@ export class SettingsPrimaryPane extends Component<ISettingsPrimaryPaneProps, {
{createIcon(settings.componentMeta?.icon, {
className: 'lc-settings-navigator-icon',
})}
<Title title={settings.componentMeta!.title} />
<span> x {settings.nodes.length}</span>
<div style={{ marginLeft: '5px' }}>
<Title title={settings.componentMeta!.title} />
<span> x {settings.nodes.length}</span>
</div>
</div>
);
}

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;
@ -266,15 +246,28 @@ export class PanelView extends Component<{
}
@observer
export class TabsPanelView extends Component<{ container: WidgetContainer<Panel> }> {
export class TabsPanelView extends Component<{
container: WidgetContainer<Panel>;
// shouldHideSingleTab: 一个布尔值,用于控制当 Tabs 组件只有一个标签时是否隐藏该标签。
shouldHideSingleTab?: boolean;
}> {
render() {
const { container } = this.props;
const titles: ReactElement[] = [];
const contents: ReactElement[] = [];
container.items.forEach((item: any) => {
titles.push(<PanelTitle key={item.id} panel={item} className="lc-tab-title" />);
contents.push(<PanelView key={item.id} panel={item} hideOperationRow hideDragLine />);
});
// 如果只有一个标签且 shouldHideSingleTab 为 true则不显示 Tabs
if (this.props.shouldHideSingleTab && container.items.length === 1) {
contents.push(<PanelView key={container.items[0].id} panel={container.items[0]} hideOperationRow hideDragLine />);
} else {
container.items.forEach((item: any) => {
titles.push(<PanelTitle key={item.id} panel={item} className="lc-tab-title" />);
contents.push(<PanelView key={item.id} panel={item} hideOperationRow hideDragLine />);
});
}
if (!titles.length) {
return contents;
}
return (
<div className="lc-tabs">
@ -315,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

@ -2,17 +2,16 @@ import { Component, Fragment } from 'react';
import classNames from 'classnames';
import { observer } from '@alilc/lowcode-editor-core';
import { Area } from '../area';
import { PanelConfig } from '../types';
import { Panel } from '../widget/panel';
import { IPublicTypePanelConfig } from '@alilc/lowcode-types';
@observer
export default class LeftFixedPane extends Component<{ area: Area<PanelConfig, Panel> }> {
export default class LeftFixedPane extends Component<{ area: Area<IPublicTypePanelConfig, Panel> }> {
componentDidUpdate() {
// FIXME: dirty fix, need deep think
this.props.area.skeleton.editor.get('designer')?.touchOffsetObserver();
}
render() {
const { area } = this.props;
const width = area.current?.config.props?.width;
@ -36,7 +35,7 @@ export default class LeftFixedPane extends Component<{ area: Area<PanelConfig, P
}
@observer
class Contents extends Component<{ area: Area<PanelConfig, Panel> }> {
class Contents extends Component<{ area: Area<IPublicTypePanelConfig, Panel> }> {
render() {
const { area } = this.props;
return <Fragment>{area.container.items.map((panel) => panel.content)}</Fragment>;

View File

@ -3,11 +3,10 @@ import classNames from 'classnames';
import { observer, Focusable } from '@alilc/lowcode-editor-core';
import { Area } from '../area';
import { Panel } from '../widget/panel';
import { PanelConfig } from '../types';
import { IPublicApiProject } from '@alilc/lowcode-types';
import { IPublicApiProject, IPublicTypePanelConfig } from '@alilc/lowcode-types';
@observer
export default class LeftFloatPane extends Component<{ area: Area<PanelConfig, Panel> }> {
export default class LeftFloatPane extends Component<{ area: Area<IPublicTypePanelConfig, Panel> }> {
private dispose?: () => void;
private focusing?: Focusable;

View File

@ -363,7 +363,7 @@ body {
flex: 1;
display: flex;
flex-direction: column;
z-index: 10;
.lc-toolbar {
display: flex;
height: var(--toolbar-height);

View File

@ -1,7 +1,6 @@
import { action, makeObservable, obx, engineConfig, IEditor, FocusTracker } from '@alilc/lowcode-editor-core';
import {
DockConfig,
PanelConfig,
WidgetConfig,
PanelDockConfig,
DialogDockConfig,
@ -29,6 +28,7 @@ import {
IPublicTypeSkeletonConfig,
IPublicApiSkeleton,
IPublicTypeConfigTransducer,
IPublicTypePanelConfig,
} from '@alilc/lowcode-types';
const logger = new Logger({ level: 'warn', bizName: 'skeleton' });
@ -70,15 +70,15 @@ export interface ISkeleton extends Omit<IPublicApiSkeleton,
readonly toolbar: Area<DockConfig | DividerConfig | PanelDockConfig | DialogDockConfig>;
readonly leftFixedArea: Area<PanelConfig, Panel>;
readonly leftFixedArea: Area<IPublicTypePanelConfig, Panel>;
readonly leftFloatArea: Area<PanelConfig, Panel>;
readonly leftFloatArea: Area<IPublicTypePanelConfig, Panel>;
readonly rightArea: Area<PanelConfig, Panel>;
readonly rightArea: Area<IPublicTypePanelConfig, Panel>;
readonly mainArea: Area<WidgetConfig | PanelConfig, Widget | Panel>;
readonly mainArea: Area<WidgetConfig | IPublicTypePanelConfig, Widget | Panel>;
readonly bottomArea: Area<PanelConfig, Panel>;
readonly bottomArea: Area<IPublicTypePanelConfig, Panel>;
readonly stages: Area<StageConfig, Stage>;
@ -104,7 +104,7 @@ export interface ISkeleton extends Omit<IPublicApiSkeleton,
defaultSetCurrent?: boolean,
): WidgetContainer;
createPanel(config: PanelConfig): Panel;
createPanel(config: IPublicTypePanelConfig): Panel;
add(config: IPublicTypeSkeletonConfig, extraConfig?: Record<string, any>): IWidget | Widget | Panel | Stage | Dock | PanelDock | undefined;
}
@ -124,15 +124,15 @@ export class Skeleton implements ISkeleton {
readonly toolbar: Area<DockConfig | DividerConfig | PanelDockConfig | DialogDockConfig>;
readonly leftFixedArea: Area<PanelConfig, Panel>;
readonly leftFixedArea: Area<IPublicTypePanelConfig, Panel>;
readonly leftFloatArea: Area<PanelConfig, Panel>;
readonly leftFloatArea: Area<IPublicTypePanelConfig, Panel>;
readonly rightArea: Area<PanelConfig, Panel>;
readonly rightArea: Area<IPublicTypePanelConfig, Panel>;
@obx readonly mainArea: Area<WidgetConfig | PanelConfig, Widget | Panel>;
@obx readonly mainArea: Area<WidgetConfig | IPublicTypePanelConfig, Widget | Panel>;
readonly bottomArea: Area<PanelConfig, Panel>;
readonly bottomArea: Area<IPublicTypePanelConfig, Panel>;
readonly stages: Area<StageConfig, Stage>;
@ -388,9 +388,9 @@ export class Skeleton implements ISkeleton {
return this.widgets.find(widget => widget.name === name);
}
createPanel(config: PanelConfig) {
createPanel(config: IPublicTypePanelConfig) {
const parsedConfig = this.parseConfig(config);
const panel = new Panel(this, parsedConfig as PanelConfig);
const panel = new Panel(this, parsedConfig as IPublicTypePanelConfig);
this.panels.set(panel.name, panel);
logger.debug(`Panel created with name: ${panel.name} \nconfig:`, config, '\n current panels: ', this.panels);
return panel;
@ -496,7 +496,7 @@ export class Skeleton implements ISkeleton {
return this.leftArea.add(parsedConfig as PanelDockConfig);
case 'rightArea':
case 'right':
return this.rightArea.add(parsedConfig as PanelConfig);
return this.rightArea.add(parsedConfig as IPublicTypePanelConfig);
case 'topArea':
case 'top':
return this.topArea.add(parsedConfig as PanelDockConfig);
@ -508,14 +508,14 @@ export class Skeleton implements ISkeleton {
case 'main':
case 'center':
case 'centerArea':
return this.mainArea.add(parsedConfig as PanelConfig);
return this.mainArea.add(parsedConfig as IPublicTypePanelConfig);
case 'bottomArea':
case 'bottom':
return this.bottomArea.add(parsedConfig as PanelConfig);
return this.bottomArea.add(parsedConfig as IPublicTypePanelConfig);
case 'leftFixedArea':
return this.leftFixedArea.add(parsedConfig as PanelConfig);
return this.leftFixedArea.add(parsedConfig as IPublicTypePanelConfig);
case 'leftFloatArea':
return this.leftFloatArea.add(parsedConfig as PanelConfig);
return this.leftFloatArea.add(parsedConfig as IPublicTypePanelConfig);
case 'stages':
return this.stages.add(parsedConfig as StageConfig);
default:

View File

@ -9,6 +9,7 @@ import {
IPublicTypeTransformedComponentMetadata,
IPublicTypeOneOfType,
ConfigureSupportEvent,
IPublicModelSettingField,
} from '@alilc/lowcode-types';
function propConfigToFieldConfig(propConfig: IPublicTypePropConfig): IPublicTypeFieldConfig {
@ -102,7 +103,7 @@ function propTypeToSetter(propType: IPublicTypePropType): IPublicTypeSetterType
},
},
isRequired,
initialValue: (field: any) => {
initialValue: (field: IPublicModelSettingField) => {
const data: any = {};
items.forEach((item: any) => {
let initial = item.defaultValue;

View File

@ -1,11 +1,11 @@
import { ReactElement, ComponentType } from 'react';
import {
IPublicTypeTitleContent,
IPublicTypeI18nData,
IPublicTypeWidgetConfigArea,
IPublicTypeWidgetBaseConfig,
IPublicTypePanelDockPanelProps,
IPublicTypePanelDockProps,
IPublicTypePanelConfigProps,
IPublicTypePanelConfig,
} from '@alilc/lowcode-types';
import { IWidget } from './widget/widget';
@ -66,40 +66,17 @@ export function isDialogDockConfig(obj: any): obj is DialogDockConfig {
return obj && obj.type === 'DialogDock';
}
// 窗格扩展
export interface PanelConfig extends IPublicTypeWidgetBaseConfig {
type: 'Panel';
content?: string | ReactElement | ComponentType<any> | PanelConfig[]; // as children
props?: PanelProps;
}
export function isPanelConfig(obj: any): obj is PanelConfig {
export function isPanelConfig(obj: any): obj is IPublicTypePanelConfig {
return obj && obj.type === 'Panel';
}
export type HelpTipConfig = string | { url?: string; content?: string | ReactElement };
export interface PanelProps extends IPublicTypePanelDockPanelProps {
title?: IPublicTypeTitleContent;
icon?: any; // 冗余字段
description?: string | IPublicTypeI18nData;
help?: HelpTipConfig; // 显示问号帮助
hiddenWhenInit?: boolean; // when this is true, by default will be hidden
condition?: (widget: IWidget) => any;
onInit?: (widget: IWidget) => any;
onDestroy?: () => any;
shortcut?: string; // 只有在特定位置,可触发 toggle show
enableDrag?: boolean; // 是否开启通过 drag 调整 宽度
keepVisibleWhileDragging?: boolean; // 是否在该 panel 范围内拖拽时保持 visible 状态
}
export interface PanelDockConfig extends IDockBaseConfig {
type: 'PanelDock';
panelName?: string;
panelProps?: PanelProps & {
panelProps?: IPublicTypePanelConfigProps & {
area?: IPublicTypeWidgetConfigArea;
};
content?: string | ReactElement | ComponentType<any> | PanelConfig[]; // content for pane
content?: string | ReactElement | ComponentType<any> | IPublicTypePanelConfig[]; // content for pane
}
export function isPanelDockConfig(obj: any): obj is PanelDockConfig {

View File

@ -1,10 +1,9 @@
import { createElement, ReactNode } from 'react';
import { obx, computed, makeObservable, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core';
import { uniqueId, createContent } from '@alilc/lowcode-utils';
import { IPublicTypeTitleContent } from '@alilc/lowcode-types';
import { IPublicTypeHelpTipConfig, IPublicTypePanelConfig, IPublicTypeTitleContent } from '@alilc/lowcode-types';
import { WidgetContainer } from './widget-container';
import { getEvent } from '@alilc/lowcode-shell';
import { PanelConfig, HelpTipConfig } from '../types';
import { TitledPanelView, TabsPanelView, PanelView } from '../components/widget-views';
import { ISkeleton } from '../skeleton';
import { composeTitle } from './utils';
@ -45,6 +44,7 @@ export class Panel implements IWidget {
if (this.container) {
return createElement(TabsPanelView, {
container: this.container,
shouldHideSingleTab: true,
});
}
@ -72,15 +72,15 @@ export class Panel implements IWidget {
readonly title: IPublicTypeTitleContent;
readonly help?: HelpTipConfig;
readonly help?: IPublicTypeHelpTipConfig;
private plain = false;
private container?: WidgetContainer<Panel, PanelConfig>;
private container?: WidgetContainer<Panel, IPublicTypePanelConfig>;
@obx.ref public parent?: WidgetContainer;
constructor(readonly skeleton: ISkeleton, readonly config: PanelConfig) {
constructor(readonly skeleton: ISkeleton, readonly config: IPublicTypePanelConfig) {
makeObservable(this);
const { name, content, props = {} } = config;
const { hideTitleBar, title, icon, description, help } = props;
@ -90,9 +90,6 @@ export class Panel implements IWidget {
this.plain = hideTitleBar || !title;
this.help = help;
if (Array.isArray(content)) {
if (content.length === 1) {
// todo: not show tabs
}
this.container = this.skeleton.createContainer(
name,
(item) => {
@ -127,7 +124,7 @@ export class Panel implements IWidget {
this.parent = parent;
}
add(item: Panel | PanelConfig) {
add(item: Panel | IPublicTypePanelConfig) {
return this.container?.add(item);
}

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.3",
"version": "1.3.2",
"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,16 @@
"license": "MIT",
"dependencies": {
"@alifd/next": "^1.19.12",
"@alilc/lowcode-designer": "1.2.3",
"@alilc/lowcode-editor-core": "1.2.3",
"@alilc/lowcode-editor-skeleton": "1.2.3",
"@alilc/lowcode-designer": "1.3.2",
"@alilc/lowcode-editor-core": "1.3.2",
"@alilc/lowcode-editor-skeleton": "1.3.2",
"@alilc/lowcode-engine-ext": "^1.0.0",
"@alilc/lowcode-plugin-designer": "1.2.3",
"@alilc/lowcode-plugin-outline-pane": "1.2.3",
"@alilc/lowcode-shell": "1.2.3",
"@alilc/lowcode-utils": "1.2.3",
"@alilc/lowcode-workspace": "1.2.3",
"@alilc/lowcode-plugin-command": "1.3.2",
"@alilc/lowcode-plugin-designer": "1.3.2",
"@alilc/lowcode-plugin-outline-pane": "1.3.2",
"@alilc/lowcode-shell": "1.3.2",
"@alilc/lowcode-utils": "1.3.2",
"@alilc/lowcode-workspace": "1.3.2",
"react": "^16.8.1",
"react-dom": "^16.8.1"
},

View File

@ -10,6 +10,7 @@ import {
Setters as InnerSetters,
Hotkey as InnerHotkey,
IEditor,
Command as InnerCommand,
} from '@alilc/lowcode-editor-core';
import {
IPublicTypeEngineOptions,
@ -19,6 +20,7 @@ import {
IPublicApiPlugins,
IPublicApiWorkspace,
IPublicEnumPluginRegisterLevel,
IPublicModelPluginContext,
} from '@alilc/lowcode-types';
import {
Designer,
@ -51,6 +53,8 @@ import {
Canvas,
Workspace,
Config,
CommonUI,
Command,
} from '@alilc/lowcode-shell';
import { isPlainObject } from '@alilc/lowcode-utils';
import './modules/live-editing';
@ -61,6 +65,8 @@ 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 { CommandPlugin } from '@alilc/lowcode-plugin-command';
import { OutlinePlugin } from '@alilc/lowcode-plugin-outline-pane';
export * from './modules/skeleton-types';
@ -77,6 +83,8 @@ 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);
await plugins.register(CommandPlugin, {});
return () => {
plugins.delete(OutlinePlugin.pluginName);
@ -85,6 +93,8 @@ async function registryInnerPlugin(designer: IDesigner, editor: IEditor, plugins
plugins.delete(defaultPanelRegistryPlugin.pluginName);
plugins.delete(builtinHotkey.pluginName);
plugins.delete(registerDefaults.pluginName);
plugins.delete(defaultContextMenu.pluginName);
plugins.delete(CommandPlugin.pluginName);
};
}
@ -95,6 +105,8 @@ globalContext.register(editor, Editor);
globalContext.register(editor, 'editor');
globalContext.register(innerWorkspace, 'workspace');
const engineContext: Partial<ILowCodePluginContextPrivate> = {};
const innerSkeleton = new InnerSkeleton(editor);
editor.set('skeleton' as any, innerSkeleton);
@ -109,8 +121,11 @@ const project = new Project(innerProject);
const skeleton = new Skeleton(innerSkeleton, 'any', false);
const innerSetters = new InnerSetters();
const setters = new Setters(innerSetters);
const innerCommand = new InnerCommand();
const command = new Command(innerCommand, engineContext as IPublicModelPluginContext);
const material = new Material(editor);
const commonUI = new CommonUI(editor);
editor.set('project', project);
editor.set('setters' as any, setters);
editor.set('material', material);
@ -131,6 +146,7 @@ const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
context.setters = setters;
context.material = material;
const eventPrefix = meta?.eventPrefix || 'common';
const commandScope = meta?.commandScope;
context.event = new Event(commonEvent, { prefix: eventPrefix });
context.config = config;
context.common = common;
@ -138,8 +154,13 @@ const pluginContextApiAssembler: ILowCodePluginContextApiAssembler = {
context.plugins = plugins;
context.logger = new Logger({ level: 'warn', bizName: `plugin:${pluginName}` });
context.workspace = workspace;
context.commonUI = commonUI;
context.command = new Command(innerCommand, context as IPublicModelPluginContext, {
commandScope,
});
context.registerLevel = IPublicEnumPluginRegisterLevel.Default;
context.isPluginRegisteredInWorkspace = false;
editor.set('pluginContext', context);
},
};
@ -148,6 +169,20 @@ plugins = new Plugins(innerPlugins).toProxy();
editor.set('innerPlugins' as any, innerPlugins);
editor.set('plugins' as any, plugins);
engineContext.skeleton = skeleton;
engineContext.plugins = plugins;
engineContext.project = project;
engineContext.setters = setters;
engineContext.material = material;
engineContext.event = event;
engineContext.logger = logger;
engineContext.hotkey = hotkey;
engineContext.common = common;
engineContext.workspace = workspace;
engineContext.canvas = canvas;
engineContext.commonUI = commonUI;
engineContext.command = command;
export {
skeleton,
plugins,
@ -161,6 +196,8 @@ export {
common,
workspace,
canvas,
commonUI,
command,
};
// 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.3",
"version": "1.3.2",
"description": "点火器bootstrap lce project",
"main": "lib/index.js",
"private": true,

View File

@ -0,0 +1,11 @@
# `@alilc/plugin-command`
> TODO: description
## Usage
```
const pluginCommand = require('@alilc/plugin-command');
// TODO: DEMONSTRATE API
```

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