Merge branch 'develop' into feat/0228

This commit is contained in:
LeoYuan 袁力皓 2023-03-10 15:28:03 +08:00 committed by GitHub
commit 7c6c758d42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
273 changed files with 6164 additions and 2742 deletions

View File

@ -19,7 +19,7 @@ module.exports = {
'no-await-in-loop': 0,
'no-plusplus': 0,
'@typescript-eslint/no-parameter-properties': 0,
'@typescript-eslint/no-unused-vars': 1,
'no-restricted-exports': ['error'],
'no-multi-assign': 1,
'no-dupe-class-members': 1,
'react/no-deprecated': 1,
@ -50,7 +50,8 @@ module.exports = {
'comma-dangle': ['error', 'always-multiline'],
'@typescript-eslint/member-ordering': [
'error',
{ default: ['signature', 'field', 'constructor', 'method'] },
{ default: ['signature', 'field', 'constructor', 'method'] }
],
'no-unused-vars': ['error', { "destructuredArrayIgnorePattern": "^_" }]
},
};

View File

@ -71,4 +71,26 @@ jobs:
working-directory: packages/react-simulator-renderer
test-script: npm test -- --jest-ci --jest-json --jest-coverage --jest-testLocationInResults --jest-outputFile=report.json
package-manager: yarn
annotations: none
cov-utils:
runs-on: ubuntu-latest
# skip fork's PR, otherwise it fails while making a comment
if: ${{ github.event.pull_request.head.repo.full_name == 'alibaba/lowcode-engine' }}
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
- uses: ArtiomTr/jest-coverage-report-action@v2
with:
working-directory: packages/utils
test-script: npm test
package-manager: yarn
annotations: none

View File

@ -24,5 +24,6 @@
- [Ychangqing](https://github.com/Ychangqing)
- [yize](https://github.com/yize)
- [youluna](https://github.com/youluna)
- [ibreathebsb](https://github.com/ibreathebsb)
如果您贡献过低代码引擎,但是没有看到您的名字,为我们的疏忽感到抱歉。欢迎您通过 PR 补充上自己的名字。

View File

@ -39,7 +39,7 @@ module.exports = {
type: 'category',
label: '扩展编辑器',
collapsed: false,
items: getDocsFromDir('guide/expand/editor'),
items: getDocsFromDir('guide/expand/editor', [{ dir: 'guide/expand/editor/parts', label: 'Parts·造物' }]),
},
{
type: 'category',

View File

@ -1,5 +1,5 @@
---
title: cavas - 画布 API
title: canvas - 画布 API
sidebar_position: 12
---
@ -17,32 +17,33 @@ sidebar_position: 12
获取拖拽操作对象的实例
```typescript
/**
* 获取拖拽操作对象的实例
* get dragon instance, you can use this to obtain draging related abilities and lifecycle hooks
* @since v1.1.0
*/
get dragon(): IPublicModelDragon | null;
```
关联模型 [IPublicModelDragon](./model/dragon)
`@type {IPublicModelDragon | null}`
相关类型:[IPublicModelDragon](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/dragon.ts)
### activeTracker
获取活动追踪器实例
```typescript
/**
* 获取活动追踪器实例
* get activeTracker instance, which is a singleton running in engine.
* it tracks document`s current focusing node/node[], and notify it`s subscribers that when
* focusing node/node[] changed.
* @since v1.1.0
*/
get activeTracker(): IPublicModelActiveTracker | null;
```
`@type {IPublicModelActiveTracker | null}`
## 方法签名
相关类型:[IPublicModelActiveTracker](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/active-tracker.ts)
### isInLiveEditing
是否处于 LiveEditing 状态
`@type {boolean}`
### clipboard
全局剪贴板实例
`@type {IPublicModelClipboard}`
相关类型:[IPublicModelClipboard](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/clipboard.ts)
## 方法
### createLocation
创建一个文档插入位置对象,该对象用来描述一个即将插入的节点在文档中的位置
@ -61,7 +62,7 @@ createLocation(locationData: IPublicTypeLocationData): IPublicModelDropLocation;
```typescript
/**
* 创建一个滚动控制器 Scroller赋予一个视图滚动的基本能力
* a Scroller is a controller that gives a view (IPublicModelScrollable) the ability scrolling
* a Scroller is a controller that gives a view (IPublicTypeScrollable) the ability scrolling
* to some cordination by api scrollTo.
*
* when a scroller is inited, will need to pass is a scrollable, which has a scrollTarget.
@ -69,7 +70,7 @@ createLocation(locationData: IPublicTypeLocationData): IPublicModelDropLocation;
* move scrollTarget`s top-left corner to (options.left, options.top) that passed in.
* @since v1.1.0
*/
createScroller(scrollable: IPublicModelScrollable): IPublicModelScroller;
createScroller(scrollable: IPublicTypeScrollable): IPublicModelScroller;
```
@ -83,4 +84,4 @@ createScroller(scrollable: IPublicModelScrollable): IPublicModelScroller;
* @since v1.1.0
*/
createScrollTarget(shell: HTMLDivElement): IPublicModelScrollTarget;
```
```

View File

@ -20,7 +20,7 @@ sidebar_position: 11
#### skeletonCabin
面板扩展相关,详见下方方法签名
## 方法签名
## 方法
### utils
#### isNodeSchema
是否为合法的 schema 结构

View File

@ -10,7 +10,7 @@ sidebar_position: 8
## 模块简介
配置模块,负责配置的读、写等操作。
## 方法签名
## 方法
### get
获取指定 key 的值

View File

@ -10,7 +10,7 @@ sidebar_position: 7
## 模块简介
负责事件处理 API支持自定义监听事件、触发事件。
## 方法签名
## 方法
### on
监听事件
@ -43,12 +43,13 @@ off(event: string, listener: (...args: any[]) => void): void;
```typescript
/**
* 取消监听事件
* cancel a monitor from a event
* 触发事件
* emit a message for a event
* @param event 事件名称
* @param listener 事件回调
* @param args 事件参数
* @returns
*/
off(event: string, listener: (...args: any[]) => void): void;
emit(event: string, ...args: any[]): void;
```
## 使用示例

View File

@ -9,7 +9,7 @@ sidebar_position: 5
## 模块简介
绑定快捷键 API可以自定义项目快捷键使用。
## 方法签名
## 方法
### bind
绑定快捷键

View File

@ -44,3 +44,7 @@ sidebar_position: 0
2. 事件events的命名格式为on[Will|Did]VerbNoun?,参考 [https://code.visualstudio.com/api/references/vscode-api#events](https://code.visualstudio.com/api/references/vscode-api#events)
3. 基于 Disposable 模式,对于事件的绑定、快捷键的绑定函数,返回值则是解绑函数
4. 对于属性的导出,统一用 .xxx 的 getter 模式,(尽量)不使用 .getXxx()
## experimental
说明此模块处于公测阶段, API 可能会发生改变.

View File

@ -8,7 +8,7 @@ sidebar_position: 10
## 模块简介
提供 init 等方法
## 方法签名
## 方法
#### 1. init
初始化引擎

View File

@ -12,7 +12,7 @@ sidebar_position: 9
> 注:日志级别可以通过 url query 动态调整,详见下方[查看示例](#查看示例)。<br/>
> 参考 [zen-logger](https://web.npm.alibaba-inc.com/package/zen-logger) 实现进行封装
## 方法签名
## 方法
日志记录方法

View File

@ -22,7 +22,7 @@ get componentsMap(): { [key: string]: IPublicTypeNpmInfo | ComponentType<any> |
```
相关类型:[IPublicTypeNpmInfo](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/npm-info.ts)
## 方法签名
## 方法
### 资产包
#### setAssets
@ -358,8 +358,11 @@ material.getRegisteredMetadataTransducers();
* add callback for assets changed event
* @param fn
*/
onChangeAssets(fn: () => void): void;
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

@ -0,0 +1,43 @@
---
title: Clipboard
sidebar_position: 14
---
> **@types** [IPublicModelClipboard](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/clipboard.ts)<br/>
> **@since** v1.1.0
## 方法
### setData
给剪贴板赋值
```typescript
/**
* 给剪贴板赋值
* set data to clipboard
*
* @param {*} data
* @since v1.1.0
*/
setData(data: any): void;
```
### waitPasteData
设置剪贴板数据设置的回调
```typescript
/**
* 设置剪贴板数据设置的回调
* set callback for clipboard provide paste data
*
* @param {KeyboardEvent} keyboardEvent
* @param {(data: any, clipboardEvent: ClipboardEvent) => void} cb
* @since v1.1.0
*/
waitPasteData(
keyboardEvent: KeyboardEvent,
cb: (data: any, clipboardEvent: ClipboardEvent) => void,
): void;
```

View File

@ -0,0 +1,173 @@
---
title: ComponentMeta
sidebar_position: 15
---
> **@types** [IPublicModelComponentMeta](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/component-meta.ts)<br/>
> **@since** v1.0.0
## 基本介绍
组件元数据信息模型
## 属性
### componentName
组件名
`@type {string}`
### isContainer
是否是「容器型」组件
`@type {boolean}`
### isMinimalRenderUnit
是否是最小渲染单元
当组件需要重新渲染时:
- 若为最小渲染单元,则只渲染当前组件,
- 若不为最小渲染单元,则寻找到上层最近的最小渲染单元进行重新渲染,直至根节点。
`@type {boolean}`
### isModal
是否为「模态框」组件
`@type {boolean}`
### configure
获取用于设置面板显示用的配置
`@type {IPublicTypeFieldConfig[]}`
相关类型:[IPublicTypeFieldConfig](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/field-config.ts)
### title
标题
`@type {string | IPublicTypeI18nData | ReactElement}`
相关类型:[IPublicTypeI18nData](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/i18n-data.ts)
### icon
图标
`@type {IPublicTypeIconType}`
相关类型:[IPublicTypeIconType](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/icon-type.ts)
### npm
组件 npm 信息
`@type {IPublicTypeNpmInfo}`
相关类型:[IPublicTypeNpmInfo](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/npm-info.ts)
### availableActions
获取元数据
`@type {IPublicTypeTransformedComponentMetadata}`
相关类型:[IPublicTypeTransformedComponentMetadata](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/transformed-component-metadata.ts)
### advanced
组件元数据中高级配置部分
`@type {IPublicTypeAdvanced}`
相关类型:[IPublicTypeAdvanced](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/advanced.ts)
## 方法
### setNpm
设置 npm 信息
```typescript
/**
* 设置 npm 信息
* set method for npm inforamtion
* @param npm
*/
setNpm(npm: IPublicTypeNpmInfo): void;
```
相关类型:[IPublicTypeNpmInfo](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/npm-info.ts)
### getMetadata
获取元数据
```typescript
/**
* 获取元数据
* get component metadata
*/
getMetadata(): IPublicTypeTransformedComponentMetadata;
```
相关类型:[IPublicTypeTransformedComponentMetadata](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/transformed-component-metadata.ts)
### checkNestingUp
检测当前对应节点是否可被放置在父节点中
```typescript
/**
* 检测当前对应节点是否可被放置在父节点中
* check if the current node could be placed in parent node
* @param my 当前节点
* @param parent 父节点
*/
checkNestingUp(my: IPublicModelNode | IPublicTypeNodeData, parent: any): boolean;
```
相关类型:
- [IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
- [IPublicTypeNodeData](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/node-data.ts)
### checkNestingDown
检测目标节点是否可被放置在父节点中
```typescript
/**
* 检测目标节点是否可被放置在父节点中
* check if the target node(s) could be placed in current node
* @param my 当前节点
* @param parent 父节点
*/
checkNestingDown(
my: IPublicModelNode | IPublicTypeNodeData,
target: IPublicTypeNodeSchema | IPublicModelNode | IPublicTypeNodeSchema[],
): boolean;
```
相关类型:
- [IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
- [IPublicTypeNodeData](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/node-data.ts)
- [IPublicTypeNodeSchema](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/node-schema.ts)
### refreshMetadata
刷新元数据,会触发元数据的重新解析和刷新
```typescript
/**
* 刷新元数据,会触发元数据的重新解析和刷新
* refresh metadata
*/
refreshMetadata(): void;
```

View File

@ -0,0 +1,113 @@
---
title: Config
sidebar_position: 16
---
> **@types** [IPublicModelEngineConfig](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/engine-config.ts)<br/>
> **@since** v1.1.3
## 方法
### has
判断指定 key 是否有值
```typescript
/**
* 判断指定 key 是否有值
* check if config has certain key configed
* @param key
* @returns
*/
has(key: string): boolean;
```
### get
获取指定 key 的值
```typescript
/**
* 获取指定 key 的值
* get value by key
* @param key
* @param defaultValue
* @returns
*/
get(key: string, defaultValue?: any): any;
```
### set
设置指定 key 的值
```typescript
/**
* 设置指定 key 的值
* set value for certain key
* @param key
* @param value
*/
set(key: string, value: any): void;
```
### setConfig
批量设值set 的对象版本
```typescript
/**
* 批量设值set 的对象版本
* set multiple config key-values
* @param config
*/
setConfig(config: { [key: string]: any }): void;
```
### getPreference
获取全局 Preference, 用于管理全局浏览器侧用户 Preference如 Panel 是否钉住
```typescript
/**
* 获取全局 Preference, 用于管理全局浏览器侧用户 Preference如 Panel 是否钉住
* get global user preference manager, which can be use to store
* user`s preference in user localstorage, such as a panel is pinned or not.
* @returns {IPublicModelPreference}
* @since v1.1.0
*/
getPreference(): IPublicModelPreference;
```
相关类型:[IPublicModelPreference](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/preference.ts)
## 事件
### onGot
获取指定 key 的值,函数回调模式,若多次被赋值,回调会被多次调用
```typescript
/**
* 获取指定 key 的值,函数回调模式,若多次被赋值,回调会被多次调用
* set callback for event of value set for some key
* this will be called each time the value is set
* @param key
* @param fn
* @returns
*/
onGot(key: string, fn: (data: any) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
### onceGot
获取指定 key 的值,若此时还未赋值,则等待,若已有值,则直接返回值
> 注:此函数返回 Promise 实例只会执行fullfill一次
```typescript
/**
* 获取指定 key 的值,若此时还未赋值,则等待,若已有值,则直接返回值
* 注:此函数返回 Promise 实例只会执行fullfill一次
* wait until value of certain key is set, will only be
* triggered once.
* @param key
* @returns
*/
onceGot(key: string): Promise<any>;
```

View File

@ -9,30 +9,65 @@ sidebar_position: 6
画布节点悬停模型
## 方法签名
### capture
capture(id: string)
hover 指定节点
### release
release(id: string)
hover 离开指定节点
### leave
leave()
清空 hover 态
## 属性
### current
当前 hover 的节点
`@type {IPublicModelNode | null}`
相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
**@since v1.0.16**
### enable
是否启用
`@type {boolean}`
## 方法
### capture
hover 指定节点
```typescript
/**
* hover 指定节点
* capture node with nodeId
* @param id 节点 id
*/
capture(id: string): void;
```
### release
hover 离开指定节点
```typescript
/**
* hover 离开指定节点
* release node with nodeId
* @param id 节点 id
*/
release(id: string): void;
```
### leave
清空 hover 态
```typescript
/**
* 清空 hover 态
* clear all hover state
*/
leave(): void;
```
## 事件
### onDetectingChange
hover 节点变化事件
@ -42,6 +77,11 @@ hover 节点变化事件
* set callback which will be called when hovering object changed.
* @since v1.1.0
*/
onDetectingChange(fn: (node: IPublicModelNode) => void): () => void;
onDetectingChange(fn: (node: IPublicModelNode | null) => void): IPublicTypeDisposable;
```
相关类型:
- [IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
- [IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
**@since v1.1.0**

View File

@ -9,7 +9,7 @@ sidebar_position: 0
文档模型
## 变量
## 属性
### id
@ -95,7 +95,7 @@ sidebar_position: 0
**@since v1.1.0**
## 方法签名
## 方法
### getNodeById
根据 nodeId 返回 [Node](./node) 实例
@ -327,27 +327,31 @@ onChangeSelection(fn: (ids: string[]) => void): IPublicTypeDisposable;
* set callback for event on visibility changed for certain node
* @param fn
*/
onChangeNodeVisible(fn: (node: IPublicModelNode, visible: boolean) => void): void;
onChangeNodeVisible(fn: (node: IPublicModelNode, visible: boolean) => void): IPublicTypeDisposable;
```
相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
- 相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
- 相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
### onChangeNodeChildren
onChangeNodeChildren(fn: (info?: IPublicTypeOnChangeOptions) => void)
当前 document 的节点 children 变更事件
```typescript
onChangeNodeChildren(fn: (info?: IPublicTypeOnChangeOptions) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
### onChangeNodeProp
当前 document 节点属性修改事件
```typescript
onChangeNodeProp(fn: (info: IPublicTypePropChangeOptions) => void)
onChangeNodeProp(fn: (info: IPublicTypePropChangeOptions) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
### onImportSchema
当前 document 导入新的 schema 事件
```typescript

View File

@ -18,7 +18,7 @@ import { IPublicModelDragon } from '@alilc/lowcode-types';
**@since** v1.1.0
## 变量
## 属性
### dragging
@ -31,7 +31,7 @@ import { IPublicModelDragon } from '@alilc/lowcode-types';
get dragging(): boolean;
```
## 方法签名
## 方法
### onDragstart

View File

@ -11,13 +11,13 @@ sidebar_position: 13
拖拽放置位置模型
## 变量
## 属性
### target
拖拽放置位置目标
`@type {IPublicModelNode}`
`@type {IPublicModelNode | null}`
相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
@ -37,7 +37,7 @@ sidebar_position: 13
相关类型:[IPublicModelLocateEvent](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/location-event.ts)
## 方法签名
## 方法
### clone

View File

@ -9,50 +9,116 @@ sidebar_position: 5
操作历史记录模型
## 方法签名
## 方法
### go
go(cursor: number)
历史记录跳转到指定位置
```typescript
/**
* 历史记录跳转到指定位置
* go to a specific history
* @param cursor
*/
go(cursor: number): void;
```
### back
back()
历史记录后退
```typescript
/**
* 历史记录后退
* go backward in history
*/
back(): void;
```
### forward
forward()
历史记录前进
```typescript
/**
* 历史记录前进
* go forward in history
*/
forward(): void;
```
### savePoint
savePoint()
保存当前状态
### isSavePoint
isSavePoint()
```typescript
/**
* 保存当前状态
* do save current change as a record in history
*/
savePoint(): void;
```
### isSavePoint
当前是否是「保存点」,即是否有状态变更但未保存
```typescript
/**
* 当前是否是「保存点」,即是否有状态变更但未保存
* check if there is unsaved change for history
*/
isSavePoint(): boolean;
```
### getState
getState()
获取 state判断当前是否为「可回退」、「可前进」的状态
```typescript
/**
* 获取 state判断当前是否为「可回退」、「可前进」的状态
* get flags in number which indicat current change state
*
* | 1 | 1 | 1 |
* | -------- | -------- | -------- |
* | modified | redoable | undoable |
* eg:
* 7 means : modified && redoable && undoable
* 5 means : modified && undoable
*/
getState(): number;
```
## 事件
### onChangeState
onChangeState(func: () => any)
监听 state 变更事件
```typescript
/**
* 监听 state 变更事件
* monitor on stateChange event
* @param func
*/
onChangeState(func: () => any): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
### onChangeCursor
onChangeCursor(func: () => any)
监听历史记录游标位置变更事件
```typescript
/**
* 监听历史记录游标位置变更事件
* monitor on cursorChange event
* @param func
*/
onChangeCursor(func: () => any): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)

View File

@ -9,40 +9,86 @@ sidebar_position: 7
模态节点管理器模型
## 方法签名
## 方法
### setNodes
setNodes()
设置模态节点,触发内部事件
```typescript
/**
* 设置模态节点,触发内部事件
* set modal nodes, trigger internal events
*/
setNodes(): void;
```
### getModalNodes
getModalNodes()
获取模态节点(们)
```typescript
/**
* 获取模态节点(们)
* get modal nodes
*/
getModalNodes(): IPublicModelNode[];
```
相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
### getVisibleModalNode
getVisibleModalNode()
获取当前可见的模态节点
```typescript
/**
* 获取当前可见的模态节点
* get current visible modal node
*/
getVisibleModalNode(): IPublicModelNode | null;
```
相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
### hideModalNodes
hideModalNodes()
隐藏模态节点(们)
```typescript
/**
* 隐藏模态节点(们)
* hide modal nodes
*/
hideModalNodes(): void;
```
### setVisible
setVisible(node: Node)
设置指定节点为可见态
```typescript
/**
* 设置指定节点为可见态
* set specific model node as visible
* @param node Node
*/
setVisible(node: IPublicModelNode): void;
```
相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
### setInvisible
setInvisible(node: Node)
设置指定节点为不可见态
```typescript
/**
* 设置指定节点为不可见态
* set specific model node as invisible
* @param node Node
*/
setInvisible(node: IPublicModelNode): void;
```
相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)

View File

@ -8,7 +8,7 @@ sidebar_position: 2
## 基本介绍
节点孩子模型
## 变量
## 属性
### owner
返回当前 children 实例所属的节点实例
@ -41,7 +41,7 @@ children 内的节点实例数
**@since v1.1.0**
## 方法签名
## 方法
### delete
删除指定节点
@ -156,6 +156,21 @@ forEach(fn: (node: IPublicModelNode, index: number) => void): void;
相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
### reverse
类似数组的 reverse
```typescript
/**
* 类似数组的 reverse
* provide the same function with {Array.prototype.reverse}
*/
reverse(): IPublicModelNode[];
```
相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
### map

View File

@ -9,7 +9,7 @@ sidebar_position: 1
节点模型
## 变量
## 属性
### id
节点 id
@ -160,7 +160,7 @@ sidebar_position: 1
`@type {IPublicModelComponentMeta | null}`
相关类型:[IPublicTypeIconType](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/component-meta.ts)
相关类型:[IPublicModelComponentMeta](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/component-meta.ts)
### document
@ -263,7 +263,7 @@ sidebar_position: 1
**@since v1.1.0**
## 方法签名
## 方法
### getRect
@ -644,4 +644,15 @@ isConditionalVisible(): boolean | undefined;
setConditionalVisible(): void;
```
**@since v1.1.0**
**@since v1.1.0**
### getDOMNode
获取节点实例对应的 dom 节点
```typescript
/**
* 获取节点实例对应的 dom 节点
*/
getDOMNode(): HTMLElement;
```

View File

@ -1,5 +1,5 @@
---
title: plugin-instance
title: PluginInstance
sidebar_position: 12
---
@ -17,38 +17,24 @@ sidebar_position: 12
插件名字
```typescript
get name(): string;
```
`@type {string}`
### dep
插件依赖
```typescript
get dep(): string[];
```
`@type {string[]}`
### disabled
插件是否禁用
```typescript
get disabled(): boolean
set disabled(disabled: boolean): void;
```
`@type {boolean}`
### meta
插件 meta 信息
```typescript
get meta(): IPublicTypePluginMeta
```
- [IPublicTypePluginMeta](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/plugin-meta.ts)
`@type {IPublicTypePluginMeta}`
相关类型:[IPublicTypePluginMeta](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/plugin-meta.ts)

View File

@ -9,7 +9,7 @@ sidebar_position: 3
属性模型
## 变量
## 属性
### id
@ -46,7 +46,7 @@ key 值
相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
## 方法签名
## 方法
### setValue

View File

@ -9,7 +9,7 @@ sidebar_position: 4
属性集模型
## 变量
## 属性
### id
id
@ -32,7 +32,7 @@ id
相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
## 方法签名
## 方法
### getProp
获取指定 path 的属性模型实例

View File

@ -0,0 +1,46 @@
---
title: Resource
sidebar_position: 13
---
> **[@experimental](./#experimental)**<br/>
> **@types** [IPublicModelResource](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/resource.ts)<br/>
> **@since** v1.1.0
## 属性
### title
资源标题
`@type {string}`
### name
资源名字
`@type {string}`
### type
资源类型
`@type {string}`
### category
资源分类
`@type {string}`
### icon
资源 icon
`@type {ReactElement}`
### options
资源配置信息
`@type {Object}`

View File

@ -9,54 +9,115 @@ sidebar_position: 6
画布节点选中模型
## 变量
## 属性
### selected
返回选中的节点 id
## 方法签名
### select
`@type {string[]}`
select(id: string)
### node
返回选中的节点(如多个节点只返回第一个)
`@type {IPublicModelNode | null}`
相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
**@since v1.1.0**
## 方法
### select
选中指定节点(覆盖方式)
### selectAll
```typescript
/**
* 选中指定节点(覆盖方式)
* select node with id, this will override current selection
* @param id
*/
select(id: string): void;
```
selectAll(ids: string[])
### selectAll
批量选中指定节点们
### remove
```typescript
/**
* 批量选中指定节点们
* select node with ids, this will override current selection
*
* @param ids
*/
selectAll(ids: string[]): void;
```
remove(id: string)
### remove
**取消选中**选中的指定节点,不会删除组件
### clear
```typescript
/**
* 移除选中的指定节点
* remove node from selection with node id
* @param id
*/
remove(id: string): void;
```
clear()
### clear
**取消选中**所有选中节点,不会删除组件
### has
```typescript
/**
* 清除所有选中节点
* clear current selection
*/
clear(): void;
```
has(id: string)
### has
判断是否选中了指定节点
### add
```typescript
/**
* 判断是否选中了指定节点
* check if node with specific id is selected
* @param id
*/
has(id: string): boolean;
```
add(id: string)
### add
选中指定节点(增量方式)
```typescript
/**
* 选中指定节点(增量方式)
* add node with specific id to selection
* @param id
*/
add(id: string): void;
```
### getNodes
getNodes()
获取选中的节点实例
```typescript
/**
* 获取选中的节点实例
* get selected nodes
*/
getNodes(): IPublicModelNode[];
```
相关类型:[IPublicModelNode](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/node.ts)
### getTopNodes
获取选区的顶层节点
例如选中的节点为:
@ -67,19 +128,34 @@ getNodes()
getNodes 返回的是 [DivA、ChildrenA、DivB]getTopNodes 返回的是 [DivA、DivB],其中 ChildrenA 由于是二层节点getTopNodes 不会返回
```typescript
/**
* 获取选区的顶层节点
* get seleted top nodes
* for example:
* getNodes() returns [A, subA, B], then
* getTopNodes() will return [A, B], subA will be removed
* @since v1.0.16
*/
getTopNodes(includeRoot?: boolean): IPublicModelNode[];
```
**@since v1.0.16**
## 事件
### onSelectionChange
注册 selection 变化事件回调
```typescript
/**
* 注册 selection 变化事件回调
* set callback which will be called when selection is changed
* @since v1.1.0
*/
onSelectionChange(fn: (ids: string[]) => void): () => void;
* 注册 selection 变化事件回调
* set callback which will be called when selection is changed
* @since v1.1.0
*/
onSelectionChange(fn: (ids: string[]) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
**@since v1.1.0**

View File

@ -3,6 +3,7 @@ title: Window
sidebar_position: 12
---
> **[@experimental](./#experimental)**<br/>
> **@types** [IPublicModelWindow](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/window.ts)<br/>
> **@since** v1.1.0
@ -11,21 +12,33 @@ sidebar_position: 12
低代码设计器窗口模型
## 变量
## 属性
### id
窗口唯一 id
`@type {string}`
### title
窗口标题
### resourceName
`@type {string}`
窗口资源名字
### icon
## 方法签名
`@type {ReactElement}`
### resource
窗口对应资源
`@type {IPublicModelResource}`
关联模型 [IPublicModelResource](./resource)
## 方法
### importSchema
当前窗口导入 schema, 会调用当前窗口对应资源的 import 钩子
@ -49,3 +62,15 @@ function changeViewType(viewName: string): void
```typescript
function save(): Promise(void)
```
## 事件
### onChangeViewType
窗口视图变更事件
```
onChangeViewType(fn: (viewName: string) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)

View File

@ -8,7 +8,7 @@ sidebar_position: 4
## 模块简介
插件管理器,提供编排模块中管理插件的能力。
## 方法签名
## 方法
### register
注册插件
@ -118,11 +118,13 @@ import { IPublicModelPluginContext } from '@alilc/lowcode-types';
const BuiltinPluginRegistry = (ctx: IPublicModelPluginContext, options: any) => {
return {
async init() {
// 1.0.4 之后的传值方式,通过 register(xxx, options)
// 取值通过 options
// 直接传值方式:
// 通过 register(xxx, options) 传入
// 通过 options 取出
// 1.0.4 之前的传值方式,通过 init(..., preference)
// 取值通过 ctx.preference.getValue()
// 引擎初始化时也可以设置某插件的全局配置项:
// 通过 engine.init(..., preference) 传入
// 通过 ctx.preference.getValue() 取出
},
};
}
@ -155,7 +157,6 @@ BuiltinPluginRegistry.meta = {
},
}
// 从 1.0.4 开始,支持直接在 pluginCreator 的第二个参数 options 获取入参
await plugins.register(BuiltinPluginRegistry, { key1: 'abc', key5: 'willNotPassToPlugin' });
```
@ -164,8 +165,11 @@ await plugins.register(BuiltinPluginRegistry, { key1: 'abc', key5: 'willNotPassT
获取指定插件
```typescript
function get(pluginName: string): IPublicModelPluginInstance;
/**
* 获取指定插件
* get plugin instance by name
*/
get(pluginName: string): IPublicModelPluginInstance | null;
```
关联模型 [IPublicModelPluginInstance](./model/plugin-instance)
@ -175,8 +179,11 @@ function get(pluginName: string): IPublicModelPluginInstance;
获取所有的插件实例
```typescript
function getAll(): IPublicModelPluginInstance[];
/**
* 获取所有的插件实例
* get all plugin instances
*/
getAll(): IPublicModelPluginInstance[];
```
关联模型 [IPublicModelPluginInstance](./model/plugin-instance)
@ -186,8 +193,11 @@ function getAll(): IPublicModelPluginInstance[];
判断是否有指定插件
```typescript
function has(pluginName: string): boolean;
/**
* 判断是否有指定插件
* check if plugin with certain name exists
*/
has(pluginName: string): boolean;
```
### delete
@ -195,8 +205,25 @@ function has(pluginName: string): boolean;
删除指定插件
```typescript
function delete(pluginName: string): void;
/**
* 删除指定插件
* delete plugin instance by name
*/
delete(pluginName: string): void;
```
### getPluginPreference
引擎初始化时可以提供全局配置给到各插件,通过这个方法可以获得本插件对应的配置
```typescript
/**
* 引擎初始化时可以提供全局配置给到各插件,通过这个方法可以获得本插件对应的配置
* use this to get preference config for this plugin when engine.init() called
*/
getPluginPreference(
pluginName: string,
): Record<string, IPublicTypePreferenceValueType> | null | undefined;
```
## 相关类型定义
@ -222,7 +249,7 @@ your-plugin/package.json
}
```
转换后的结构:
```json
```typescript
const debug = (ctx: IPublicModelPluginContext, options: any) => {
return {};
}

View File

@ -69,7 +69,7 @@ get simulatorHost(): IPublicApiSimulatorHost | null;
相关类型:[IPublicApiSimulatorHost](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/api/simulator-host.ts)
## 方法签名
## 方法
### openDocument
打开一个 document

View File

@ -8,7 +8,7 @@ sidebar_position: 6
## 模块简介
负责注册设置器、管理设置器的 API。注册自定义设置器之后可以在物料中进行使用。
## 方法签名
## 方法
### getSetter
获取指定 setter

View File

@ -135,7 +135,7 @@ skeleton.add({
});
```
## 方法签名
## 方法
### add
@ -295,9 +295,11 @@ hideArea(areaName: string): void;
* @param listener
* @returns
*/
onShowPanel(listener: (...args: any[]) => void): () => void;
onShowPanel(listener: (...args: any[]) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
### onHidePanel
监听 Panel 实例隐藏事件
@ -309,9 +311,11 @@ onShowPanel(listener: (...args: any[]) => void): () => void;
* @param listener
* @returns
*/
onHidePanel(listener: (...args: any[]) => void): () => void;
onHidePanel(listener: (...args: any[]) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
### onShowWidget
@ -324,9 +328,10 @@ onHidePanel(listener: (...args: any[]) => void): () => void;
* @param listener
* @returns
*/
onShowWidget(listener: (...args: any[]) => void): () => void;
onShowWidget(listener: (...args: any[]) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
### onHideWidget
@ -339,9 +344,11 @@ onShowWidget(listener: (...args: any[]) => void): () => void;
* @param listener
* @returns
*/
onHideWidget(listener: (...args: any[]) => void): () => void;
onHideWidget(listener: (...args: any[]) => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
## 使用示例
```typescript

View File

@ -3,10 +3,10 @@ title: workspace - 应用级 API
sidebar_position: 12
---
> **[@experimental](./#experimental)**<br/>
> **@types** [IPublicApiWorkspace](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/api/workspace.ts)<br/>
> **@since** v1.1.0
## 模块简介
通过该模块可以开发应用级低代码设计器。
@ -47,30 +47,100 @@ get window(): IPublicModelWindow[]
关联模型 [IPublicModelWindow](./model/window)
## 方法签名
### resourceList
当前设计器的资源列表数据
```
get resourceList(): IPublicModelResource;
```
关联模型 [IPublicModelResource](./model/resource)
## 方法
### registerResourceType
注册资源
```typescript
/** 注册资源 */
registerResourceType(resourceName: string, resourceType: 'editor', options: IPublicResourceOptions): void;
registerResourceType(resourceTypeModel: IPublicTypeResourceType): void;
```
相关类型:[IPublicTypeResourceType](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/resource-type.ts)
### setResourceList
设置设计器资源列表数据
```typescript
setResourceList(resourceList: IPublicResourceList) {}
```
相关类型:[IPublicResourceOptions](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/resource-options.ts)
### openEditorWindow
打开视图窗口
```typescript
openEditorWindow(resourceName: string, title: string, options: Object, viewName?: string): void;
```
### openEditorWindowById
通过视图 id 打开窗口
```typescript
openEditorWindowById(id: string): void;
```
### removeEditorWindow
移除视图窗口
```typescript
removeEditorWindow(resourceName: string, title: string): void;
```
### removeEditorWindowById
通过视图 id 移除窗口
```typescript
removeEditorWindowById(id: string): void;
```
## 事件
### onChangeWindows
窗口新增/删除的事件
```typescript
function onChangeWindows(fn: () => void): void;
function onChangeWindows(fn: () => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
### onChangeActiveWindow
active 窗口变更事件
```typescript
function onChangeActiveWindow(fn: () => void): void;
function onChangeActiveWindow(fn: () => void): IPublicTypeDisposable;
```
相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)
### onResourceListChange
设计器资源列表数据变更事件
```typescript
onResourceListChange(fn: (resourceList: IPublicResourceList): void): (): IPublicTypeDisposable;
```
- 相关类型:[IPublicResourceOptions](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/resource-options.ts)
- 相关类型:[IPublicTypeDisposable](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/type/disposable.ts)

View File

@ -4,4 +4,4 @@ sidebar_position: 15
tags: [FAQ]
---
你可以通过在线工具「Parts 造物」生产物料描述协议,然后使用到你的项目中去。
文档地址:[利用 Parts 造物快速使用 react 组件](/site/docs/guide/expand/editor/partsIntro)
文档地址:[利用 Parts 造物快速使用 react 组件](/site/docs/guide/expand/editor/parts/partsIntro)

View File

@ -23,7 +23,7 @@ sidebar_position: 1
对于低代码物料来说A 平台创建的物料无法使用到 B 平台上,如果想在 B 平台实现同样的物料,需要按照 B 平台的标准搭建一份物料。
对于 ProCode 物料来说,需要在低代码平台进行消费,是需要进行转换的,包括搭建配置项的生成、物料搭建图等,可能还需要特殊的描述文件进行描述。由于这一层没有统一,同一份 ProCode 物料每接入一个低代码,可能需要的描述文件格式不同,转换的代码不同,使用的工具也不同。
对于 ProCode 物料来说,需要在低代码平台进行消费,是需要进行转换的,包括搭建配置项的生成、物料搭建图等,可能还需要特殊的描述文件进行描述。由于这一层没有统一,同一份 ProCode 物料每接入一个低代码,可能需要的描述文件格式不同,转换的代码不同,使用的工具也不同。
### 生态隔离

View File

@ -1,6 +1,6 @@
---
title: 低代码生态脚手架 & 调试机制
sidebar_position: 7
sidebar_position: 8
---
## 脚手架简述

View File

@ -10,7 +10,7 @@ sidebar_position: 2
## 可视化生成物料描述
使用 Parts 造物平台:[使用文档](/site/docs/guide/expand/editor/partsIntro)
使用 Parts 造物平台:[使用文档](/site/docs/guide/expand/editor/parts/partsIntro)
## 自动生成物料描述

View File

@ -0,0 +1,4 @@
{
"label": "Parts 造物",
"position": 1
}

View File

@ -0,0 +1,18 @@
---
title: 介绍
sidebar_position: 1
---
## 介绍
![](https://gw.alicdn.com/imgextra/i2/O1CN01Gyq6AZ1nOENPTVXX7_!!6000000005079-2-tps-256-104.png)
「Parts·造物」是基于开源低代码引擎打造的次时代物料研发和集成工具一方面作为低代码引擎搭建低代码平台的一个样板展示开源生态下的各个组件如何集合在一起形成生产力另一方面也可以生产低代码平台所需的物料。
目前「Parts·造物」主要提供两大产品功能
1. React 组件导入低代码引擎:通过在线可视化的「物料描述」配置,任意工具开发的 React 组件都可以快速完成对低代码引擎的适配,导入到低代码引擎项目中进行使用。不必额外开发新的组件。
2. 低代码生产组件:通过低代码的形式生产组件,极低上手门槛,提供丰富的原子组件用于组合,完善的调试预览和组件生命周期控制。生产的组件既可以在低代码引擎项目中使用,也可以出码后在普通源码项目中使用。
## 联系我们
<img src="https://img.alicdn.com/imgextra/i2/O1CN01UF88Xi1jC5SZ6m4wt_!!6000000004511-2-tps-750-967.png" width="300" />

View File

@ -0,0 +1,267 @@
---
title: 资产包管理
sidebar_position: 4
---
## 介绍
通过前述介绍,相信大家已经了解如何使用「[Parts·造物](https://parts.lowcode-engine.cn/)」来将已有的 React 组件快速接入低代码引擎,以及生产低代码组件。
大家在使用的过程中,可能会希望构建出来的资产包可以后续随时访问下载,或者希望构建资产包时各个组件的版本等信息可以持久化起来并且能够多人维护。
通过「[Parts·造物](https://parts.lowcode-engine.cn/)」的 `资产包` 管理功能帮助大家解决这个问题
![image.png](https://img.alicdn.com/imgextra/i3/O1CN01Fkaznh1zWj9wYKpcH_!!6000000006722-2-tps-1702-628.png)
## 新建资产包
首先,我们在 我的资产包 tab 中点击 `新建资产包`
![image.png](https://img.alicdn.com/imgextra/i1/O1CN01qe8zfO1ilysebSfD5_!!6000000004454-2-tps-3064-1432.png)
- 填写资产包名称
- 配置资产包管理员,管理员拥有该资产包的所有权限,初始默认为资产包的创建者,还可以添加其他人作为管理员,
- 配置资产包描述 (可选)
- 点击 `确定`, 即可完成资产包的创建
接下来需要为资产包添加一个或者多个组件。
## 添加组件
第二步:新建完资产包以后,我们就可以为其添加组件了,如果是新建资产包流程,新建完成之后会自动弹出组件配置的弹窗,当然,你可可以通过点击资产包卡片的方式打开组件配置的弹窗。
![image.png](https://img.alicdn.com/imgextra/i3/O1CN01kqymdB1nkDQclPk7F_!!6000000005127-2-tps-965-261.png)
- 点击弹窗中 `添加组件` 按钮,在弹出的组件选择面板中,选中需要添加的组件并点击 `下一步`
![image.png](https://img.alicdn.com/imgextra/i1/O1CN014Baihf1r742Qi1Wel_!!6000000005583-2-tps-1856-1520.png)
- 进入组件版本以及描述协议版本选择界面,选择所需要的正确版本,点击 `安装` 即可完成一个组件的添加。
![image.png](https://img.alicdn.com/imgextra/i2/O1CN01Y7aWWi1MMPDVlidgz_!!6000000001420-2-tps-1668-1462.png)
## 构建资产包
添加完组件以后就点击 `保存并构建资产包` 进入资产包构建配置弹窗
![image.png](https://img.alicdn.com/imgextra/i4/O1CN01iZf4Ue1PlXnyKYxnK_!!6000000001881-2-tps-1288-670.png)
- `开启缓存` : 可充分利用之前的构建结果缓存来加速资产包的生成,我们会将每个组件的构建结果以 包名和版本号为 key 进行缓存。
- `任务描述` : 当前构建任务的一些描述信息。
点击 `确认` 按钮 会自动跳转到当前资产包的构建历史界面:
![image.png](https://img.alicdn.com/imgextra/i2/O1CN01krDaFc1TuTztMPssI_!!6000000002442-2-tps-1726-696.png)
构建历史界面会显示当前资产包所有的构建历史记录,表格状态栏展示了构建的状态:`成功`,`失败`,`正在运行` 三种状态,操作列可以在构建成功时复制或者下载资产包结果
## 使用资产包
你可以在 [lowcode-demo](https://github.com/alibaba/lowcode-demo) 中直接引用,可直接替换 demo 中原来的资产包文件:
例如,在 [demo-lowcode-component](https://github.com/alibaba/lowcode-demo/tree/main/demo-lowcode-component) 中,直接用你的资产包文件替换文件[assets.json](https://github.com/alibaba/lowcode-demo/blob/main/demo-lowcode-component/src/services/assets.json),即可快速使用自己的物料了。
### 在编辑器中使用资产包
在使用含有低代码组件的资产包注意 注意引擎版本必须大于等于 `1.1.0-beta.9`
然后直接替换 [lowcode-demo](https://github.com/alibaba/lowcode-demo) demo 中的 `assets.json` 文件即可。
### 在预览中使用资产包
在预览中使用资产包的整体思路是从 `资产包` 中提取并转换出 `ReactRenderer` 渲染所需要的 react 组件列表 (`components` 参数),然后将 `schema` 以及 `components` 传入到 `ReactRenderer` 中进行渲染,需要注意的是,在 `资产包` 的转换过程中,我们也需要将 `低代码组件` 转换成 react 组件,具体逻辑可以参考下 [demo-lowcode-component](https://github.com/alibaba/lowcode-demo/tree/main/demo-lowcode-component) 中 `src/parse-assets.ts` 文件的实现。
基于资产包进行预览的整体逻辑如下: [详见](https://github.com/alibaba/lowcode-demo/blob/main/demo-lowcode-component/src/preview.tsx)
```ts
import ReactDOM from 'react-dom';
import React, { useState } from 'react';
import { Loading } from '@alifd/next';
import ReactRenderer from '@alilc/lowcode-react-renderer';
import { createFetchHandler } from '@alilc/lowcode-datasource-fetch-handler';
import {
getProjectSchemaFromLocalStorage,
} from './services/mockService';
import assets from './services/assets.json';
import { parseAssets } from './parse-assets';
const getScenarioName = function () {
if (location.search) {
return new URLSearchParams(location.search.slice(1)).get('scenarioName') || 'index';
}
return 'index';
};
const SamplePreview = () => {
const [data, setData] = useState({});
async function init() {
const scenarioName = getScenarioName();
const projectSchema = getProjectSchemaFromLocalStorage(scenarioName);
const { componentsMap: componentsMapArray, componentsTree } = projectSchema;
const schema = componentsTree[0];
const componentsMap: any = {};
componentsMapArray.forEach((component: any) => {
componentsMap[component.componentName] = component;
});
// 特别提醒重点注意!!!:从资产包中解析出所有的 react 组件列表
const { components } = await parseAssets(assets);
setData({
schema,
components,
});
}
const { schema, components } = data;
if (!schema || !components) {
init();
return <Loading fullScreen />;
}
return (
<div className="lowcode-plugin-sample-preview">
<ReactRenderer
className="lowcode-plugin-sample-preview-content"
schema={schema}
// // 将 react 组件列表传入 ReactRenderer 进行渲染
components={components}
appHelper={{
requestHandlersMap: {
fetch: createFetchHandler(),
},
}}
/>
</div>
);
};
ReactDOM.render(<SamplePreview />, document.getElementById('ice-container'));
```
从资产包中解析 react 组件列表的逻辑如下,[详见](https://github.com/alibaba/lowcode-demo/blob/main/demo-lowcode-component/src/parse-assets.ts)
```ts
import { ComponentDescription, ComponentSchema, RemoteComponentDescription } from '@alilc/lowcode-types';
import { buildComponents, AssetsJson, AssetLoader } from '@alilc/lowcode-utils';
import ReactRenderer from '@alilc/lowcode-react-renderer';
import { injectComponents } from '@alilc/lowcode-plugin-inject';
import React, { createElement } from 'react';
export async function parseAssets(assets: AssetsJson) {
const { components: rawComponents, packages } = assets;
const libraryAsset = [];
const libraryMap = {};
const packagesMap = {};
packages.forEach(pkg => {
const { package: _package, library, urls, renderUrls, id } = pkg;
if (_package) {
libraryMap[id || _package] = library;
}
packagesMap[id || _package] = pkg;
if (renderUrls) {
libraryAsset.push(renderUrls);
} else if (urls) {
libraryAsset.push(urls);
}
});
const assetLoader = new AssetLoader();
await assetLoader.load(libraryAsset);
let newComponents = rawComponents;
if (rawComponents && rawComponents.length) {
const componentDescriptions: ComponentDescription[] = [];
const remoteComponentDescriptions: RemoteComponentDescription[] = [];
rawComponents.forEach((component: any) => {
if (!component) {
return;
}
if (component.exportName && component.url) {
remoteComponentDescriptions.push(component);
} else {
componentDescriptions.push(component);
}
});
newComponents = [...componentDescriptions];
// 如果有远程组件描述协议,则自动加载并补充到资产包中,同时出发 designer.incrementalAssetsReady 通知组件面板更新数据
if (remoteComponentDescriptions && remoteComponentDescriptions.length) {
await Promise.all(
remoteComponentDescriptions.map(async (component: any) => {
const { exportName, url, npm } = component;
await (new AssetLoader()).load(url);
function setAssetsComponent(component: any, extraNpmInfo: any = {}) {
const components = component.components;
if (Array.isArray(components)) {
components.forEach(d => {
newComponents = newComponents.concat({
npm: {
...npm,
...extraNpmInfo,
},
...d,
} || []);
});
return;
}
newComponents = newComponents.concat({
npm: {
...npm,
...extraNpmInfo,
},
...component.components,
} || []);
}
function setArrayAssets(value: any[], preExportName: string = '', preSubName: string = '') {
value.forEach((d: any, i: number) => {
const exportName = [preExportName, i.toString()].filter(d => !!d).join('.');
const subName = [preSubName, i.toString()].filter(d => !!d).join('.');
Array.isArray(d) ? setArrayAssets(d, exportName, subName) : setAssetsComponent(d, {
exportName,
subName,
});
});
}
if (window[exportName]) {
if (Array.isArray(window[exportName])) {
setArrayAssets(window[exportName] as any);
} else {
setAssetsComponent(window[exportName] as any);
}
}
return window[exportName];
}),
);
}
}
const lowcodeComponentsArray = [];
const proCodeComponentsMap = newComponents.reduce((acc, cur) => {
if ((cur.devMode || '').toLowerCase() === 'lowcode') {
lowcodeComponentsArray.push(cur);
} else {
acc[cur.componentName] = {
...(cur.reference || cur.npm),
componentName: cur.componentName,
};
}
return acc;
}, {})
function genLowCodeComponentsMap(components) {
const lowcodeComponentsMap = {};
lowcodeComponentsArray.forEach((lowcode) => {
const id = lowcode.reference?.id;
const schema = packagesMap[id]?.schema;
const comp = genLowcodeComp(schema, {...components, ...lowcodeComponentsMap});
lowcodeComponentsMap[lowcode.componentName] = comp;
});
return lowcodeComponentsMap;
}
let components = await injectComponents(buildComponents(libraryMap, proCodeComponentsMap));
const lowCodeComponents = genLowCodeComponentsMap(components);
return {
components: { ...components, ...lowCodeComponents }
}
}
function genLowcodeComp(schema: ComponentSchema, components: any) {
return class LowcodeComp extends React.Component {
render(): React.ReactNode {
return createElement(ReactRenderer, {
...this.props,
schema,
components,
designMode: '',
});
}
};
}
```
## 联系我们
<img src="https://img.alicdn.com/imgextra/i2/O1CN01UF88Xi1jC5SZ6m4wt_!!6000000004511-2-tps-750-967.png" width="300" />

View File

@ -0,0 +1,92 @@
---
title: 低代码组件
sidebar_position: 2
---
## 什么是低代码组件
我们先了解一下什么是低代码组件,为什么要用低代码组件。
低代码组件是通过可视化的方式生产的组件,这些组件既可以用于低代码搭建体系,也可以用于 ProCode 开发体系(后续迭代)。
那么为什么我们要使用低代码的形式来开发组件:
* <font color="red"><b>首先</b></font><b>轻快</b>,低代码组件只需通过浏览器秒级完成初始化工作,不需要 ProCode 繁重的环境准备;<b>环境一致(低代码环境)</b>,同时能够保证物料的开发环境和真实的运行环境是一致的,不会存在开发和运行环境不一致的问题。
* <font color="red"><b>其次</b></font><b>通用能力可视化方式抽象,提升研发效能</b>,比如获取远程数据、视图开发、依赖管理、生命周期、事件绑定等功能。
<font color="red">低代码组件不是用来替代 ProCode 的开发方式</font>,而是让开发者可以从 ProCode 中重复的工作脱离出来,抽象更多业务垂直的能力,从而起到提效的作用。
## 创建组件
环境准备:我们可以通过 Parts 提供的通用[低代码组件开发环境](https://parts.lowcode-engine.cn/material#/)开发。
点击开发新组件 --> 填写组件标题 --> 填写组件名称 --> 点击确定,完成组件创建工作。
![](https://img.alicdn.com/imgextra/i2/O1CN01OTQRew25y6WxuONIx_!!6000000007594-2-tps-3396-1696.png)
## 组件开发
一张图速览低代码组件开发的功能模块,其中大部分功能可以参考[低代码引擎文档](https://lowcode-engine.cn/site/docs/guide/quickStart/intro)。
![](https://img.alicdn.com/imgextra/i1/O1CN01gx96E121qzv4smV2v_!!6000000007037-2-tps-3456-1930.png)
### 依赖管理
依赖管理用于管理低代码组件本身的依赖(类似于 dependencies。步骤点击添加组件 --> 选择安装的组件 --> 保存并构建 (需要等待几分钟构建)。
![](https://img.alicdn.com/imgextra/i4/O1CN01wC9JPK1J9dKLca9wK_!!6000000000986-2-tps-1438-819.png)
### 属性定义
用于定义组件接收外部传入的 propTypes组件内部可以通过<font color="red">this.props.${属性名称}</font>的方式获取属性值。
属性定义前建议先阅读 [物料描述详解](https://lowcode-engine.cn/site/docs/guide/expand/editor/metaSpec)、[预置设置器](https://lowcode-engine.cn/site/docs/guide/appendix/setters)。
![](https://img.alicdn.com/imgextra/i2/O1CN01wesIJA1nL1eSPrk7U_!!6000000005072-2-tps-1438-821.png)
![](https://img.alicdn.com/imgextra/i3/O1CN01FZIRwv1es9lGplgIB_!!6000000003926-2-tps-1438-821.png)
### 生命周期
低代码组件的开发支持 componentDidMount、componentDidUpdate、componentDidCatch、componentWillUnmount 几个生命周期
![](https://img.alicdn.com/imgextra/i4/O1CN010bnrxJ1oLlujlfFqj_!!6000000005209-2-tps-1438-819.png)
### 组件调试
我们提供了一套线上实时调试的方案,只需点击右上角的调试按钮,就能自动创建一个低代码应用,在这个应用中可以实时调试当前的低代码组件。
![](https://img.alicdn.com/imgextra/i2/O1CN01Tk96vp1xrDeNeIUJD_!!6000000006496-2-tps-1438-820.png)
在低代码应用中使用,组件面板 --> 低代码组件,找到对应的低代码组件拖入画布即可。
![](https://img.alicdn.com/imgextra/i2/O1CN01oGHLea1lzDAhZQQVO_!!6000000004889-2-tps-1438-819.png)
### 组件发布
同时我们提供了组件发布的功能,用于组件版本管理,点击右上角的发布按钮即可发布组件
![](https://img.alicdn.com/imgextra/i2/O1CN017suVAD1NXEC8zQgO1_!!6000000001579-2-tps-1438-821.png)
## 组件使用
组件的消费是通过资产包来管理的,详情请参考 [资产包管理](./partsassets)。
## 组件导出
开发好的低代码组件可以导出成为 React 组件,脱离低代码引擎独立使用。同时导出功能也为您的组件留出一份备份,您可以放心使用本产品的服务,而不用担心万一出现的不能服务的场景。
在物料列表页面,低代码组件会有一个导出的动作。
![](https://img.alicdn.com/imgextra/i2/O1CN016oUByO21spVHZvvw2_!!6000000007041-2-tps-1395-413.png)
点击导出后,就会开启导出低代码组件的过程。这个过程持续 10s+,导出完成后会为您自动下载对应的 zip 包。
![](https://img.alicdn.com/imgextra/i1/O1CN01lctpIo1aDcEvu75Mo_!!6000000003296-2-tps-1399-512.png)
zip 包解压后可以看到一个完整的组件脚手架工程,您可以在这个工程里继续开发调试,或者发布到合适的 npm 源中。
![](https://img.alicdn.com/imgextra/i1/O1CN010aAjsf1xYRPZBAh7d_!!6000000006455-2-tps-2154-1072.png)
注意:目前导出功能暂不支持 低代码组件嵌套。
## 联系我们
<img src="https://img.alicdn.com/imgextra/i2/O1CN01UF88Xi1jC5SZ6m4wt_!!6000000004511-2-tps-750-967.png" width="300" />

View File

@ -1,5 +1,5 @@
---
title: 利用 Parts 造物快速使用 react 组件
title: React 组件导入
sidebar_position: 3
---
## 介绍
@ -50,8 +50,6 @@ sidebar_position: 3
![image.png](https://img.alicdn.com/imgextra/i3/O1CN01klci7y1IUPflKpeVB_!!6000000000896-2-tps-1193-704.png)
#### 给组件增加物料描述
选中刚刚新增的 BlockPicker 组件,然后给它增加描述:
- 打开左侧 Setter 面板
- 按照组件的属性拖入需要 Setter 类型(如图中组件的 width 属性,拖入数字 Setter
- 各种 Setter 的介绍可以参看这篇文档:[预置设置器列表](/site/docs/guide/appendix/setters)
@ -102,31 +100,22 @@ sidebar_position: 3
- 点击确定发布完成
![image.png](https://img.alicdn.com/imgextra/i4/O1CN01uwa8RH1QDwM7FN31k_!!6000000001943-2-tps-1431-734.png)
## 资产包构建
## 资产包
第三步:物料描述发布完成后,接下来我们就需要构建出可用的资产包用于低代码应用中。
#### 资产包构建
有两种方式可以构建资产包:
- 一种是通过 [`我的资产包`] 资产包管理模块进行整个资产包生命周期的管理,当然也包括资产包的构建,可参考 [资产包管理](./partsassets)
- 一种是通过 [`我的物料`] 组件物料管理模块的 `资产包构建` 进行构建, 具体操作如下:
- 选择需要构建的组件
- 点击构建资产包按钮
- 选择刚刚的物料描述配置
- 开始构建,构建完成后你将得到一份 json 文件(里面包含了物料描述和 umd 包),就可以到项目中使用了
![image.png](https://img.alicdn.com/imgextra/i3/O1CN01Oc73aw1TH5vlJx9oj_!!6000000002356-2-tps-1431-770.png)
- 选择需要构建的组件
- 点击构建资产包按钮
- 选择刚刚的物料描述配置
- 开始构建,构建完成后你将得到一份 json 文件(里面包含了物料描述和 umd 包),就可以到项目中使用了
#### 资产包使用
详情请参考 [资产包管理](./partsassets#使用资产包)
**方式一、在 **[**lowcode-demo**](https://github.com/alibaba/lowcode-demo)**中直接引用,可直接替换 demo 中原来的资产包文件:**
## 联系我们
例如,在 basic-fusion demo 中,直接用你的资产包文件替换文件[assets.json](https://github.com/alibaba/lowcode-demo/blob/main/demo-basic-fusion/src/services/assets.json),即可快速使用自己的物料了。
**方式二、将新的资产包内容和现有的资产包内容融合:**
将上面构建完成的资产包与你项目中的[assets.json 文件](https://github.com/alibaba/lowcode-demo/blob/main/demo-basic-fusion/src/services/assets.json)合并,主要合并 packages 和 components。
- packages 中是构建好的 umd 包;
- components 中是上面配置好的[物料描述](https://lowcode-engine.cn/material),你也可以在基础上二次加工;
![image.png](https://img.alicdn.com/imgextra/i3/O1CN01m7QkDN1P7hL86mjyH_!!6000000001794-2-tps-1140-744.png)
<img src="https://img.alicdn.com/imgextra/i2/O1CN01UF88Xi1jC5SZ6m4wt_!!6000000004511-2-tps-750-967.png" width="300" />

View File

@ -2,7 +2,9 @@
title: 插件扩展 - 编排扩展
sidebar_position: 6
---
## 场景一:扩展选中节点操作项
### 增加节点操作项
![image.png](https://img.alicdn.com/imgextra/i2/O1CN01J7PrJc1S86XNDBIFQ_!!6000000002201-2-tps-1240-292.png)
@ -31,7 +33,7 @@ const addHelloAction = (ctx: IPublicModelPluginContext) => {
},
important: true,
});
}
},
};
};
addHelloAction.pluginName = 'addHelloAction';
@ -68,6 +70,7 @@ await plugins.register(removeCopyAction);
具体 API 参考:[API 文档](/site/docs/api/material#removebuiltincomponentaction)
## 实际案例
### 区块管理
- 仓库地址:[https://github.com/alibaba/lowcode-plugins](https://github.com/alibaba/lowcode-plugins)

View File

@ -2,6 +2,7 @@
title: 插件扩展 - 面板扩展
sidebar_position: 5
---
## 插件简述
插件功能赋予低代码引擎更高的灵活性,低代码引擎的生态提供了一些官方的插件,但是无法满足所有人的需求,所以提供了强大的插件定制功能。
@ -23,7 +24,7 @@ import { IPublicModelPluginContext } from '@alilc/lowcode-types';
const pluginA = (ctx: IPublicModelPluginContext, options: any) => {
return {
init() {
console.log(options.key);
console.log(options.key);
// 往引擎增加面板
ctx.skeleton.add({
// area 配置见下方说明
@ -36,9 +37,9 @@ const pluginA = (ctx: IPublicModelPluginContext, options: any) => {
},
destroy() {
console.log('我被销毁了~');
}
}
}
},
};
};
pluginA.pluginName = 'pluginA';
@ -56,6 +57,7 @@ plugins.register(pluginA, { key: 'test' });
![image.png](https://img.alicdn.com/imgextra/i3/O1CN01y05ZHC1Gix0p4nXxH_!!6000000000657-2-tps-3068-1648.png)
### 展示区域 area
#### topArea
展示在设计器的顶部区域,常见的相关区域的插件主要是:、
@ -76,6 +78,7 @@ plugins.register(pluginA, { key: 'test' });
4. JS 等代码面板。
可以发现,这个区域的面板大多数操作时是不需要同时并存的,且交互比较复杂的,需要一个更整块的区域来进行操作。
#### centerArea
画布区域,由于画布大多数是展示作用,所以一般扩展的种类比较少。常见的扩展有:
@ -106,12 +109,12 @@ PanelDock 是以面板的形式展示在设计器的左侧区域的。其中主
接入可以参考代码
```javascript
import { skeleton } from "@alilc/lowcode-engine";
import { skeleton } from '@alilc/lowcode-engine';
skeleton.add({
area: "leftArea", // 插件区域
type: "PanelDock", // 插件类型,弹出面板
name: "sourceEditor",
area: 'leftArea', // 插件区域
type: 'PanelDock', // 插件类型,弹出面板
name: 'sourceEditor',
content: SourceEditor, // 插件组件实例
props: {
align: "left",
@ -139,12 +142,12 @@ Widget 形式是直接渲染在当前编辑器的对应位置上。如 demo 中
接入可以参考代码:
```javascript
import {skeleton} from "@alilc/lowcode-engine";
import { skeleton } from '@alilc/lowcode-engine';
// 注册 logo 面板
skeleton.add({
area: "topArea",
type: "Widget",
name: "logo",
area: 'topArea',
type: 'Widget',
name: 'logo',
content: Logo, // Widget 组件实例
contentProps: { // Widget 插件 props
logo:
@ -152,7 +155,7 @@ skeleton.add({
href: "/",
},
props: {
align: "left",
align: 'left',
width: 100,
},
});
@ -163,7 +166,7 @@ skeleton.add({
一个图标的表现形式,可以用于语言切换、跳转到外部链接、打开一个 widget 等场景。
```javascript
import { skeleton } from "@alilc/lowcode-engine";
import { skeleton } from '@alilc/lowcode-engine';
skeleton.add({
area: 'leftArea',
@ -176,12 +179,12 @@ skeleton.add({
props: {
align: 'bottom',
},
onClick: function() {
onClick: function () {
// 打开外部链接
window.open('https://lowcode-engine.cn');
// 显示 widget
skeleton.showWidget('xxx');
}
},
});
```
@ -211,4 +214,4 @@ skeleton.add({
- [阿里巴巴低代码引擎项目实战 (11)-区块管理 - ICON 优化](https://www.bilibili.com/video/BV1zr4y1H7Km/)
- [阿里巴巴低代码引擎项目实战 (11)-区块管理 - 自动截图](https://www.bilibili.com/video/BV1GZ4y117VH/)
- [阿里巴巴低代码引擎项目实战 (11)-区块管理 - 样式优化](https://www.bilibili.com/video/BV1Pi4y1S7ZT/)
- [阿里低代码引擎项目实战 (12)-区块管理 (完结)-给引擎插件提个 PR](https://www.bilibili.com/video/BV1hB4y1277o/)
- [阿里低代码引擎项目实战 (12)-区块管理 (完结)-给引擎插件提个 PR](https://www.bilibili.com/video/BV1hB4y1277o/)

View File

@ -1,6 +1,6 @@
---
title: 设置器扩展
sidebar_position: 4
sidebar_position: 5
---
## 设置器简述

View File

@ -132,7 +132,7 @@ Demo 根据**不同的设计器所需要的物料不同**,分为了下面的 8
介绍下其中主要的内容
- 设计器入口文件 `source/index.ts` 这个文件做了下述几个事情:
- 设计器入口文件 `src/index.ts` 这个文件做了下述几个事情:
- 通过 plugins.register 注册各种插件,包括官方插件 (已发布 npm 包形式的插件) 和 `plugins` 目录下内置的示例插件
- 通过 init 初始化低代码设计器
- plugins 目录,存放的都是示例插件,方便用户从中看到一个插件是如何实现的

View File

@ -84,7 +84,7 @@ component // 组件名称, 比如 biz-button
```
#### README.md
##### README.md
- README.md 应该包含业务组件的源信息、使用说明以及 API示例如下
@ -126,7 +126,7 @@ npm install @alifd/ice-layout -S
| type | type | String | `primray``normal` | normal |
```
#### package.json
##### package.json
`package.json` 中包含了一些依赖信息和配置信息,示例如下:
```json
@ -159,7 +159,7 @@ npm install @alifd/ice-layout -S
}
```
#### src/index.js
##### src/index.js
包含组件的出口文件,示例如下:
@ -178,7 +178,7 @@ export default Button;
import Button, { Group } form '@scope/button';
```
#### src/index.scss
##### src/index.scss
```css
/* 不引入依赖组件的样式,比如组件 import { Button } from '@alifd/next'; */
@ -193,7 +193,7 @@ import Button, { Group } form '@scope/button';
}
```
#### demo
##### demo
demo 目录存放的是组件的文档,无文档的业务组件无法带来任何价值,因此 demo 是必选项。demo 目录下的文件采取 markdown 的写法可以是多个文件示例demo/basic.md如下
demo/basic.md
@ -236,12 +236,12 @@ ReactDOM.render(<div className="test">
API 是组件的属性解释,给开发者作为组件属性配置的参考。为了保持 API 的一致性,我们制定这个 API 命名规范。对于业界通用的,约定俗成的命名,我们遵循社区的约定。对于业界有多种规则难以确定的,我们确定其中一种,大家共同遵守。
#### 通用规则
##### 通用规则
- 所有的 API 采用小驼峰的书写规则,如 `onChange``direction``defaultVisible`
- 标签名采用大驼峰书写规则,如 `Menu``Slider``DatePicker`
#### 通用命名
##### 通用命名
| API 名称 | 类型 | 描述 | 常见变量 |
| :------------- | :------------- | :----------------------------------------------------------- | :---------------------------------------------------- |
@ -261,7 +261,7 @@ API 是组件的属性解释,给开发者作为组件属性配置的参考。
| has+'属性' | boolean | 拥有某个属性 | 例如 `hasArrow` `hasHeader` `hasClose` 等等 |
#### 多选枚举
##### 多选枚举
当某个 API 的接口,允许用户指定多个枚举值的时候,我们把这个接口定义为多选枚举。一个很典型的例子是某个弹层组件的 `closable` 属性,我们会允许:键盘 esc 按键、点击 mask、点击 close 按钮、点击组件以外的任何区域进行关闭。
@ -280,11 +280,11 @@ true 表示触发规则都会关闭false 表示触发规则不会关闭。
- `<Dialog closable={false} />`,任何情况下都不关闭,只能通过受控设置 visible
- `<Dialog closable closeMode={['close', 'esc']} />`,用户按 esc 或者点击关闭按钮会关闭
#### 事件
##### 事件
- 标准事件或者自定义的符合 w3c 标准的事件,命名必须 on 开头, 即 `on` + 事件名,如 onExpand。
#### 表单规范
##### 表单规范
- 支持[受控模式](https://reactjs.org/docs/forms.html#controlled-components)(value + onChange) A)
- value 控制组件数据展现
@ -292,7 +292,7 @@ true 表示触发规则都会关闭false 表示触发规则不会关闭。
- `value={undefined}`的时候清空数据field 的 reset 函数会给所有组件下发 undefined 数据 (AA))
- 一次完整操作抛一次 onChange 事件 `建议` 比如有 Process 表示进展中的状态,建议增加 API `onProcess`;如果有 Start 表示启动状态,建议增加 API `onStart`  (AA)
#### 属性的传递
##### 属性的传递
**1. 原子组件Atomic Component**
> 最小粒子,不能再拆分的组件
@ -354,7 +354,7 @@ $ iceworks sync
文件命名采取 [bcp47](https://tools.ietf.org/html/bcp47) 规范
#### 目录规范
##### 目录规范
在 `src` 目录新增 `locale` 目录用于管理不同语言的文案。
@ -367,7 +367,7 @@ $ iceworks sync
|------ ja-JP.js
```
#### 定义不同的语言
##### 定义不同的语言
```javascript
// zh-CN.js
@ -390,7 +390,7 @@ export default {
};
```
#### 组件支持多语言建议方案
##### 组件支持多语言建议方案
```jsx
// index.jsx
@ -417,7 +417,7 @@ export default class BizHello extends Component {
}
```
#### 组件支持全局替换国际化文案
##### 组件支持全局替换国际化文案
配合 ConfigProvider 支持全局替换国际化文案。
@ -451,7 +451,7 @@ export default ConfigProvider.config(BizHello, {
业务组件中如果有自定义的需要跟随主题色的 UI一定要引入变量的形式增加组件的流通性。
#### src/index.scss
##### src/index.scss
```css
/* 如果需要引入主题变量引入此段 */
@ -503,7 +503,7 @@ api 属性标准参考 [https://fusion.design/help.html#/dev-biz](https://fusio
无障碍需要符合 [WCAG 2.1 A 级标准](https://www.w3.org/TR/WCAG21/),可参考 [W3C 无障碍最佳实践](https://www.w3.org/TR/wai-aria-practices-1.1/)、[Fusion 无障碍指引 2.3.1](https://alibaba-fusion.github.io/next/part1/basics.html) 章节等。
#### 增加 a11y.md 无障碍 demo
##### 增加 a11y.md 无障碍 demo
必须借助 API 才能完成无障碍工作的组件必须为开发者提供无障碍的使用文档,请[参考](https://fusion.design/pc/component/select?themeid=2#accessibility-container)组件 API 中 `ARIA and Keyboard` ,建议在 `demo` 目录新增 `a11y.md` 文件用于演示组件的无障碍使用。
@ -517,7 +517,7 @@ component
详细指引查看无障碍开发指南 [https://alibaba-fusion.github.io/next/part1/basics.html](https://alibaba-fusion.github.io/next/part1/basics.html)。
#### 通过键盘快速访问
##### 通过键盘快速访问
一般键盘事件有 Up Arrow/Down Arrow/Enter/Esc/Tab
@ -531,7 +531,7 @@ component
| Esc | 关闭列表 |
#### 对读屏软件友好
##### 对读屏软件友好
- 对于组件,我们为开发者内置 `role` 和特定 `aria-_属性`,开发者也可以对非组件 API 属性都可以透传至 DOM 元素,进行修改 `role``aria-_参数`,但是要注意对应关系,请[参考](https://alibaba-fusion.github.io/next/part1/WAI-ARIA.html)。
- 对一些特殊的组件传递参数才能支持无障碍,设置 `id``autoFocus` 和传参数,如下:
@ -925,18 +925,18 @@ props 数组下对象字段描述:
|initialChildren | 组件拖入“设计器”时根据此配置自动生成 children 节点 schema |NodeData[]/Function NodeData[] | ((target: SettingTarget) => NodeData[]);|
|getResizingHandlers| 用于配置设计器中组件 resize 操作工具的样式和内容 | Function| (currentNode: any) => Array<{ type: 'N' | 'W' | 'S' | 'E' | 'NW' | 'NE' | 'SE' | 'SW'; content?: ReactElement; propTarget?: string; appearOn?: 'mouse-enter' | 'mouse-hover' | 'selected' | 'always'; }> / ReactElement[];
|callbacks| 配置 callbacks 可捕获引擎抛出的一些事件,例如 onNodeAdd、onResize 等 | Callback| -
|callbacks.onNodeAdd| 在容器中拖入组件时触发的事件回调| Function| (e: MouseEvent, currentNode: any) => any
|callbacks.onNodeRemove| 在容器中删除组件时触发的事件回调| Function| (e: MouseEvent, currentNode: any) => any
|callbacks.onNodeAdd| 在容器中拖入组件时触发的事件回调 | Function| (e: MouseEvent, currentNode: any) => any
|callbacks.onNodeRemove| 在容器中删除组件时触发的事件回调 | Function| (e: MouseEvent, currentNode: any) => any
|callbacks.onResize| 调整容器尺寸时触发的事件回调,常常与 getResizingHandlers 搭配使用 | Function| 详见 Types 定义
|callbacks.onResizeStart| 调整容器尺寸开始时触发的事件回调,常常与 getResizingHandlers 搭配使用 | Function| 详见 Types 定义
|callbacks.onResizeEnd| 调整容器尺寸结束时触发的事件回调,常常与 getResizingHandlers 搭配使用 | Function| 详见 Types 定义
|callbacks.onSubtreeModified| 容器节点结构树发生变化时触发的回调| Function| (currentNode: any, options: any) => void;
|callbacks.onMouseDownHook| 鼠标按下操作回调| Function| (e: MouseEvent, currentNode: any) => any;
|callbacks.onClickHook| 鼠标单击操作回调| Function| (e: MouseEvent, currentNode: any) => any;
|callbacks.onDblClickHook| 鼠标双击操作回调| Function| (e: MouseEvent, currentNode: any) => any;
|callbacks.onSubtreeModified| 容器节点结构树发生变化时触发的回调 | Function| (currentNode: any, options: any) => void;
|callbacks.onMouseDownHook| 鼠标按下操作回调 | Function| (e: MouseEvent, currentNode: any) => any;
|callbacks.onClickHook| 鼠标单击操作回调 | Function| (e: MouseEvent, currentNode: any) => any;
|callbacks.onDblClickHook| 鼠标双击操作回调 | Function| (e: MouseEvent, currentNode: any) => any;
|callbacks.onMoveHook| 节点被拖动回调 | Function| (currentNode: any) => boolean;
|callbacks.onHoverHook| 节点被 hover 回调 | Function| (currentNode: any) => boolean;
|callbacks.onChildMoveHook| 容器节点的子节点被拖动回调| Function| (childNode: any, currentNode: any) => boolean;
|callbacks.onChildMoveHook| 容器节点的子节点被拖动回调 | Function| (childNode: any, currentNode: any) => boolean;
描述举例:
@ -1543,7 +1543,7 @@ block/
```
#### 入口文件
##### 入口文件
(/src/index.jsx)
@ -1559,7 +1559,7 @@ const App = hot(router);
ReactDOM.render(<App />, document.getElementById(pkg.config && pkg.config.targetRootID || 'root'));
```
#### 应用参数配置文件
##### 应用参数配置文件
(/src/config/app.js)
@ -1596,7 +1596,7 @@ export default {
}
```
#### 应用扩展配置规范:
##### 应用扩展配置规范:
(/src/utils/index.js)
@ -1618,7 +1618,7 @@ export default {
}
```
#### 应用常量配置
##### 应用常量配置
(/src/config/constants.js)
@ -1628,7 +1628,7 @@ export default {
}
```
#### 应用样式配置
##### 应用样式配置
(/src/global.scss)

View File

@ -39,7 +39,6 @@ const config = {
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: {
sidebarPath: require.resolve('./config/sidebars.js'),
@ -55,7 +54,6 @@ const config = {
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
docs: {
sidebar: {
@ -76,7 +74,7 @@ const config = {
metadata: [{ name: 'referrer', content: 'no-referrer' }],
tableOfContents: {
minHeadingLevel: 2,
maxHeadingLevel: 5,
maxHeadingLevel: 6,
},
}),

View File

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

View File

@ -8,9 +8,14 @@ module.exports = function getDocsFromDir(dir, cateList) {
const baseDir = path.join(__dirname, '../docs/');
const docsDir = path.join(baseDir, dir);
function isNil(value) {
return value === undefined || value === null;
}
function getMarkdownOrder(filepath) {
const data = matter(fs.readFileSync(filepath, 'utf-8')).data;
return (data || {}).sidebar_position || 100;
const { data } = matter(fs.readFileSync(filepath, 'utf-8'));
const { sidebar_position } = data || {};
return isNil(sidebar_position) ? 100 : sidebar_position;
}
const docs = glob.sync('*.md?(x)', {
@ -19,17 +24,17 @@ module.exports = function getDocsFromDir(dir, cateList) {
});
const result = docs
.filter(doc => !/^index.md(x)?$/.test(doc))
.map(doc => {
.filter((doc) => !/^index.md(x)?$/.test(doc))
.map((doc) => {
return path.join(docsDir, doc);
})
.sort((a, b) => {
const orderA = getMarkdownOrder(a);
const orderB = getMarkdownOrder(b);
return orderB - orderA;
return orderA - orderB;
})
.map(filepath => {
.map((filepath) => {
// /Users/xxx/site/docs/guide/basic/router.md => guide/basic/router
const id = path
.relative(baseDir, filepath)
@ -38,7 +43,7 @@ module.exports = function getDocsFromDir(dir, cateList) {
return id;
});
(cateList || []).forEach(item => {
(cateList || []).forEach((item) => {
const { dir, subCategory, ...otherConfig } = item;
const indexList = glob.sync('index.md?(x)', {
cwd: path.join(baseDir, dir),

View File

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

View File

@ -143,5 +143,6 @@
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
}
},
"repository": "git@github.com:alibaba/lowcode-engine.git"
}

View File

@ -15,6 +15,7 @@ const jestConfig = {
// testMatch: ['**/document-model.test.ts'],
// testMatch: ['**/prop.test.ts'],
// testMatch: ['(/tests?/.*(test))\\.[jt]s$'],
// testMatch: ['**/document/node/node.add.test.ts'],
transformIgnorePatterns: [
`/node_modules/(?!${esModules})/`,
],

View File

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

View File

@ -76,7 +76,7 @@ export class BorderDetecting extends Component<{ host: BuiltinSimulatorHost }> {
const { host } = this.props;
const { current } = this;
const canHoverHook = current?.componentMeta.getMetadata()?.configure.advanced?.callbacks?.onHoverHook;
const canHoverHook = current?.componentMeta.advanced.callbacks?.onHoverHook;
const canHover = (canHoverHook && typeof canHoverHook === 'function') ? canHoverHook(current.internalToShellNode()) : true;
if (!canHover || !current || host.viewport.scrolling || host.liveEditing.editing) {

View File

@ -136,40 +136,40 @@ export class BoxResizingInstance extends Component<{
this.willBind();
const resize = (e: MouseEvent, direction: string, node: any, moveX: number, moveY: number) => {
const metadata = node.componentMeta.getMetadata();
const { advanced } = node.componentMeta;
if (
metadata.configure?.advanced?.callbacks &&
typeof metadata.configure.advanced.callbacks.onResize === 'function'
advanced.callbacks &&
typeof advanced.callbacks.onResize === 'function'
) {
(e as any).trigger = direction;
(e as any).deltaX = moveX;
(e as any).deltaY = moveY;
const cbNode = node?.isNode ? node.internalToShellNode() : node;
metadata.configure.advanced.callbacks.onResize(e, cbNode);
advanced.callbacks.onResize(e, cbNode);
}
};
const resizeStart = (e: MouseEvent, direction: string, node: any) => {
const metadata = node.componentMeta.getMetadata();
const { advanced } = node.componentMeta;
if (
metadata.configure?.advanced?.callbacks &&
typeof metadata.configure.advanced.callbacks.onResizeStart === 'function'
advanced.callbacks &&
typeof advanced.callbacks.onResizeStart === 'function'
) {
(e as any).trigger = direction;
const cbNode = node?.isNode ? node.internalToShellNode() : node;
metadata.configure.advanced.callbacks.onResizeStart(e, cbNode);
advanced.callbacks.onResizeStart(e, cbNode);
}
};
const resizeEnd = (e: MouseEvent, direction: string, node: any) => {
const metadata = node.componentMeta.getMetadata();
const { advanced } = node.componentMeta;
if (
metadata.configure?.advanced?.callbacks &&
typeof metadata.configure.advanced.callbacks.onResizeEnd === 'function'
advanced.callbacks &&
typeof advanced.callbacks.onResizeEnd === 'function'
) {
(e as any).trigger = direction;
const cbNode = node?.isNode ? node.internalToShellNode() : node;
metadata.configure.advanced.callbacks.onResizeEnd(e, cbNode);
advanced.callbacks.onResizeEnd(e, cbNode);
}
const workspace = globalContext.get('workspace');
@ -236,17 +236,22 @@ export class BoxResizingInstance extends Component<{
render() {
const { observed } = this.props;
if (!observed.hasOffset) {
return null;
}
const { node, offsetWidth, offsetHeight, offsetTop, offsetLeft } = observed;
let triggerVisible: any = [];
const metadata = node.componentMeta.getMetadata();
if (metadata.configure?.advanced?.getResizingHandlers) {
triggerVisible = metadata.configure.advanced.getResizingHandlers(node.internalToShellNode());
let offsetWidth = 0;
let offsetHeight = 0;
let offsetTop = 0;
let offsetLeft = 0;
if (observed.hasOffset) {
offsetWidth = observed.offsetWidth;
offsetHeight = observed.offsetHeight;
offsetTop = observed.offsetTop;
offsetLeft = observed.offsetLeft;
const { node } = observed;
const metadata = node.componentMeta.getMetadata();
if (metadata.configure?.advanced?.getResizingHandlers) {
triggerVisible = metadata.configure.advanced.getResizingHandlers(node.internalToShellNode());
}
}
triggerVisible = normalizeTriggers(triggerVisible);
const baseSideClass = 'lc-borders lc-resize-side';
@ -254,90 +259,100 @@ export class BoxResizingInstance extends Component<{
return (
<div>
{triggerVisible.includes('N') && (
<div
ref={(ref) => { this.outlineN = ref; }}
className={classNames(baseSideClass, 'n')}
style={{
height: 20,
transform: `translate(${offsetLeft}px, ${offsetTop - 10}px)`,
width: offsetWidth,
}}
/>
)}
{triggerVisible.includes('NE') && (
<div
ref={(ref) => { this.outlineNE = ref; }}
className={classNames(baseCornerClass, 'ne')}
style={{
transform: `translate(${offsetLeft + offsetWidth - 5}px, ${offsetTop - 3}px)`,
cursor: 'nesw-resize',
}}
/>
)}
{triggerVisible.includes('E') && (
<div
className={classNames(baseSideClass, 'e')}
ref={(ref) => { this.outlineE = ref; }}
style={{
height: offsetHeight,
transform: `translate(${offsetLeft + offsetWidth - 10}px, ${offsetTop}px)`,
width: 20,
}}
/>
)}
{triggerVisible.includes('SE') && (
<div
ref={(ref) => { this.outlineSE = ref; }}
className={classNames(baseCornerClass, 'se')}
style={{
transform: `translate(${offsetLeft + offsetWidth - 5}px, ${offsetTop + offsetHeight - 5}px)`,
cursor: 'nwse-resize',
}}
/>
)}
{triggerVisible.includes('S') && (
<div
ref={(ref) => { this.outlineS = ref; }}
className={classNames(baseSideClass, 's')}
style={{
height: 20,
transform: `translate(${offsetLeft}px, ${offsetTop + offsetHeight - 10}px)`,
width: offsetWidth,
}}
/>
)}
{triggerVisible.includes('SW') && (
<div
ref={(ref) => { this.outlineSW = ref; }}
className={classNames(baseCornerClass, 'sw')}
style={{
transform: `translate(${offsetLeft - 3}px, ${offsetTop + offsetHeight - 5}px)`,
cursor: 'nesw-resize',
}}
/>
)}
{triggerVisible.includes('W') && (
<div
ref={(ref) => { this.outlineW = ref; }}
className={classNames(baseSideClass, 'w')}
style={{
height: offsetHeight,
transform: `translate(${offsetLeft - 10}px, ${offsetTop}px)`,
width: 20,
}}
/>
)}
{triggerVisible.includes('NW') && (
<div
ref={(ref) => { this.outlineNW = ref; }}
className={classNames(baseCornerClass, 'nw')}
style={{
transform: `translate(${offsetLeft - 3}px, ${offsetTop - 3}px)`,
cursor: 'nwse-resize',
}}
/>
)}
<div
ref={(ref) => {
this.outlineN = ref;
}}
className={classNames(baseSideClass, 'n')}
style={{
height: 20,
transform: `translate(${offsetLeft}px, ${offsetTop - 10}px)`,
width: offsetWidth,
display: triggerVisible.includes('N') ? 'flex' : 'none',
}}
/>
<div
ref={(ref) => {
this.outlineNE = ref;
}}
className={classNames(baseCornerClass, 'ne')}
style={{
transform: `translate(${offsetLeft + offsetWidth - 5}px, ${offsetTop - 3}px)`,
cursor: 'nesw-resize',
display: triggerVisible.includes('NE') ? 'flex' : 'none',
}}
/>
<div
className={classNames(baseSideClass, 'e')}
ref={(ref) => {
this.outlineE = ref;
}}
style={{
height: offsetHeight,
transform: `translate(${offsetLeft + offsetWidth - 10}px, ${offsetTop}px)`,
width: 20,
display: triggerVisible.includes('E') ? 'flex' : 'none',
}}
/>
<div
ref={(ref) => {
this.outlineSE = ref;
}}
className={classNames(baseCornerClass, 'se')}
style={{
transform: `translate(${offsetLeft + offsetWidth - 5}px, ${
offsetTop + offsetHeight - 5
}px)`,
cursor: 'nwse-resize',
display: triggerVisible.includes('SE') ? 'flex' : 'none',
}}
/>
<div
ref={(ref) => {
this.outlineS = ref;
}}
className={classNames(baseSideClass, 's')}
style={{
height: 20,
transform: `translate(${offsetLeft}px, ${offsetTop + offsetHeight - 10}px)`,
width: offsetWidth,
display: triggerVisible.includes('S') ? 'flex' : 'none',
}}
/>
<div
ref={(ref) => {
this.outlineSW = ref;
}}
className={classNames(baseCornerClass, 'sw')}
style={{
transform: `translate(${offsetLeft - 3}px, ${offsetTop + offsetHeight - 5}px)`,
cursor: 'nesw-resize',
display: triggerVisible.includes('SW') ? 'flex' : 'none',
}}
/>
<div
ref={(ref) => {
this.outlineW = ref;
}}
className={classNames(baseSideClass, 'w')}
style={{
height: offsetHeight,
transform: `translate(${offsetLeft - 10}px, ${offsetTop}px)`,
width: 20,
display: triggerVisible.includes('W') ? 'flex' : 'none',
}}
/>
<div
ref={(ref) => {
this.outlineNW = ref;
}}
className={classNames(baseCornerClass, 'nw')}
style={{
transform: `translate(${offsetLeft - 3}px, ${offsetTop - 3}px)`,
cursor: 'nwse-resize',
display: triggerVisible.includes('NW') ? 'flex' : 'none',
}}
/>
</div>
);
}

View File

@ -46,7 +46,7 @@ export class BorderSelectingInstance extends Component<{
dragging,
});
const hideSelectTools = observed.node.componentMeta.getMetadata().configure.advanced?.hideSelectTools;
const { hideSelectTools } = observed.node.componentMeta.advanced;
if (hideSelectTools) {
return null;

View File

@ -121,7 +121,7 @@ export class InsertionView extends Component<{ host: BuiltinSimulatorHost }> {
return null;
}
// 如果是个绝对定位容器,不需要渲染插入标记
if (loc.target.componentMeta.getMetadata().configure.advanced?.isAbsoluteLayoutContainer) {
if (loc.target?.componentMeta?.advanced.isAbsoluteLayoutContainer) {
return null;
}

View File

@ -99,6 +99,7 @@ export interface BuiltinSimulatorProps {
simulatorUrl?: Asset;
theme?: Asset;
componentsAsset?: Asset;
// eslint-disable-next-line @typescript-eslint/member-ordering
[key: string]: any;
}
@ -184,40 +185,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
*/
autoRender = true;
stopAutoRepaintNode() {
this.renderer?.stopAutoRepaintNode();
}
enableAutoRepaintNode() {
this.renderer?.enableAutoRepaintNode();
}
constructor(project: Project, designer: Designer) {
makeObservable(this);
this.project = project;
this.designer = designer;
this.scroller = this.designer.createScroller(this.viewport);
this.autoRender = !engineConfig.get('disableAutoRender', false);
this.componentsConsumer = new ResourceConsumer<Asset | undefined>(() => this.componentsAsset);
this.injectionConsumer = new ResourceConsumer(() => {
return {
appHelper: engineConfig.get('appHelper'),
};
});
this.i18nConsumer = new ResourceConsumer(() => this.project.i18n);
transactionManager.onStartTransaction(() => {
this.stopAutoRepaintNode();
}, IPublicEnumTransitionType.REPAINT);
// 防止批量调用 transaction 时,执行多次 rerender
const rerender = debounce(this.rerender.bind(this), 28);
transactionManager.onEndTransaction(() => {
rerender();
this.enableAutoRepaintNode();
}, IPublicEnumTransitionType.REPAINT);
}
get currentDocument() {
return this.project.currentDocument;
}
@ -285,6 +252,87 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
@obx.ref _props: BuiltinSimulatorProps = {};
@obx.ref private _contentWindow?: Window;
get contentWindow() {
return this._contentWindow;
}
@obx.ref private _contentDocument?: Document;
get contentDocument() {
return this._contentDocument;
}
private _renderer?: BuiltinSimulatorRenderer;
get renderer() {
return this._renderer;
}
readonly asyncLibraryMap: { [key: string]: {} } = {};
readonly libraryMap: { [key: string]: string } = {};
private _iframe?: HTMLIFrameElement;
private disableHovering?: () => void;
private disableDetecting?: () => void;
readonly liveEditing = new LiveEditing();
@obx private instancesMap: {
[docId: string]: Map<string, IPublicTypeComponentInstance[]>;
} = {};
private tryScrollAgain: number | null = null;
private _sensorAvailable = true;
/**
* @see IPublicModelSensor
*/
get sensorAvailable(): boolean {
return this._sensorAvailable;
}
private sensing = false;
constructor(project: Project, designer: Designer) {
makeObservable(this);
this.project = project;
this.designer = designer;
this.scroller = this.designer.createScroller(this.viewport);
this.autoRender = !engineConfig.get('disableAutoRender', false);
this.componentsConsumer = new ResourceConsumer<Asset | undefined>(() => this.componentsAsset);
this.injectionConsumer = new ResourceConsumer(() => {
return {
appHelper: engineConfig.get('appHelper'),
};
});
this.i18nConsumer = new ResourceConsumer(() => this.project.i18n);
transactionManager.onStartTransaction(() => {
this.stopAutoRepaintNode();
}, IPublicEnumTransitionType.REPAINT);
// 防止批量调用 transaction 时,执行多次 rerender
const rerender = debounce(this.rerender.bind(this), 28);
transactionManager.onEndTransaction(() => {
rerender();
this.enableAutoRepaintNode();
}, IPublicEnumTransitionType.REPAINT);
}
stopAutoRepaintNode() {
this.renderer?.stopAutoRepaintNode();
}
enableAutoRepaintNode() {
this.renderer?.enableAutoRepaintNode();
}
/**
* @see ISimulator
*/
@ -337,30 +385,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
this.viewport.mount(viewport);
}
@obx.ref private _contentWindow?: Window;
get contentWindow() {
return this._contentWindow;
}
@obx.ref private _contentDocument?: Document;
get contentDocument() {
return this._contentDocument;
}
private _renderer?: BuiltinSimulatorRenderer;
get renderer() {
return this._renderer;
}
readonly asyncLibraryMap: { [key: string]: {} } = {};
readonly libraryMap: { [key: string]: string } = {};
private _iframe?: HTMLIFrameElement;
/**
* {
* "title":"BizCharts",
@ -544,7 +568,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
return;
}
// 触发 onMouseDownHook 钩子
const onMouseDownHook = node.componentMeta?.getMetadata()?.configure.advanced?.callbacks?.onMouseDownHook;
const onMouseDownHook = node.componentMeta.advanced.callbacks?.onMouseDownHook;
if (onMouseDownHook) {
onMouseDownHook(downEvent, node.internalToShellNode());
}
@ -689,10 +713,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
);
}
private disableHovering?: () => void;
private disableDetecting?: () => void;
/**
*
*/
@ -742,8 +762,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
// };
}
readonly liveEditing = new LiveEditing();
setupLiveEditing() {
const doc = this.contentDocument!;
// cause edit
@ -880,9 +898,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
// return this.renderer?.createComponent(schema) || null;
}
@obx private instancesMap: {
[docId: string]: Map<string, IPublicTypeComponentInstance[]>;
} = {};
setInstance(docId: string, id: string, instances: IPublicTypeComponentInstance[] | null) {
if (!hasOwnProperty(this.instancesMap, docId)) {
this.instancesMap[docId] = new Map();
@ -1045,8 +1060,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
};
}
private tryScrollAgain: number | null = null;
/**
* @see ISimulator
*/
@ -1107,15 +1120,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
this.renderer?.clearState();
}
private _sensorAvailable = true;
/**
* @see IPublicModelSensor
*/
get sensorAvailable(): boolean {
return this._sensorAvailable;
}
/**
* @see IPublicModelSensor
*/
@ -1160,8 +1164,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
);
}
private sensing = false;
/**
* @see IPublicModelSensor
*/
@ -1180,7 +1182,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
const { nodes } = dragObject as IPublicTypeDragNodeObject;
const operationalNodes = nodes?.filter((node) => {
const onMoveHook = node.componentMeta?.getMetadata()?.configure.advanced?.callbacks?.onMoveHook;
const onMoveHook = node.componentMeta?.advanced.callbacks?.onMoveHook;
const canMove = onMoveHook && typeof onMoveHook === 'function' ? onMoveHook(node.internalToShellNode()) : true;
let parentContainerNode: Node | null = null;
@ -1195,7 +1197,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
parentNode = parentNode.parent;
}
const onChildMoveHook = parentContainerNode?.componentMeta?.getMetadata()?.configure.advanced?.callbacks?.onChildMoveHook;
const onChildMoveHook = parentContainerNode?.componentMeta?.advanced.callbacks?.onChildMoveHook;
const childrenCanMove = onChildMoveHook && parentContainerNode && typeof onChildMoveHook === 'function' ? onChildMoveHook(node.internalToShellNode(), parentContainerNode.internalToShellNode()) : true;

View File

@ -3,6 +3,9 @@ import { IPublicTypeNodeSchema, IPublicTypeComponentInstance, IPublicTypeNodeIns
export interface BuiltinSimulatorRenderer {
readonly isSimulatorRenderer: true;
autoRepaintNode?: boolean;
components: Record<string, Component>;
rerender: () => void;
createComponent(schema: IPublicTypeNodeSchema): Component | null;
getComponent(componentName: string): Component;
getClosestNodeInstance(

View File

@ -10,12 +10,12 @@ import {
IPublicTypeI18nData,
IPublicTypePluginConfig,
IPublicTypeFieldConfig,
IPublicTypeMetadataTransducer,
IPublicModelComponentMeta,
IPublicTypeAdvanced,
} from '@alilc/lowcode-types';
import { deprecate, isRegExp, isTitleConfig } from '@alilc/lowcode-utils';
import { deprecate, isRegExp, isTitleConfig, isNode } from '@alilc/lowcode-utils';
import { computed, createModuleEventBus, IEventBus } from '@alilc/lowcode-editor-core';
import { isNode, Node, INode } from './document';
import { Node, INode } from './document';
import { Designer } from './designer';
import {
IconContainer,
@ -56,8 +56,8 @@ export function buildFilter(rule?: string | string[] | RegExp | IPublicTypeNesti
return (testNode: Node | IPublicTypeNodeSchema) => list.includes(testNode.componentName);
}
export interface IComponentMeta extends IPublicModelComponentMeta {
export interface IComponentMeta extends IPublicModelComponentMeta<INode> {
prototype?: any;
}
export class ComponentMeta implements IComponentMeta {
@ -111,7 +111,7 @@ export class ComponentMeta implements IComponentMeta {
private _transformedMetadata?: IPublicTypeTransformedComponentMetadata;
get configure() {
get configure(): IPublicTypeFieldConfig[] {
const config = this._transformedMetadata?.configure;
return config?.combined || config?.props || [];
}
@ -140,7 +140,7 @@ export class ComponentMeta implements IComponentMeta {
// string | i18nData | ReactElement
// TitleConfig title.label
if (isTitleConfig(this._title)) {
return (this._title.label as any) || this.componentName;
return (this._title?.label as any) || this.componentName;
}
return this._title || this.componentName;
}
@ -161,6 +161,16 @@ export class ComponentMeta implements IComponentMeta {
return this._acceptable!;
}
get advanced(): IPublicTypeAdvanced {
return this.getMetadata().configure.advanced || {};
}
/**
* @legacy compatiable for vision
* @deprecated
*/
prototype?: any;
constructor(readonly designer: Designer, metadata: IPublicTypeComponentMetadata) {
this.parseMetadata(metadata);
}
@ -211,7 +221,7 @@ export class ComponentMeta implements IComponentMeta {
: title;
}
const liveTextEditing = this._transformedMetadata.configure.advanced?.liveTextEditing || [];
const liveTextEditing = this.advanced.liveTextEditing || [];
function collectLiveTextEditing(items: IPublicTypeFieldConfig[]) {
items.forEach((config) => {
@ -231,7 +241,7 @@ export class ComponentMeta implements IComponentMeta {
collectLiveTextEditing(this.configure);
this._liveTextEditing = liveTextEditing.length > 0 ? liveTextEditing : undefined;
const isTopFixed = this._transformedMetadata.configure.advanced?.isTopFixed;
const isTopFixed = this.advanced.isTopFixed;
if (isTopFixed) {
this._isTopFixed = isTopFixed;
@ -263,8 +273,11 @@ export class ComponentMeta implements IComponentMeta {
this.parseMetadata(this.getMetadata());
}
private transformMetadata(metadta: IPublicTypeComponentMetadata): IPublicTypeTransformedComponentMetadata {
const result = this.designer.componentActions.getRegisteredMetadataTransducers().reduce((prevMetadata, current) => {
private transformMetadata(
metadta: IPublicTypeComponentMetadata,
): IPublicTypeTransformedComponentMetadata {
const registeredTransducers = this.designer.componentActions.getRegisteredMetadataTransducers();
const result = registeredTransducers.reduce((prevMetadata, current) => {
return current(prevMetadata);
}, preprocessMetadata(metadta));
@ -347,8 +360,6 @@ export class ComponentMeta implements IComponentMeta {
};
}
// compatiable vision
prototype?: any;
}
export function isComponentMeta(obj: any): obj is ComponentMeta {
@ -373,4 +384,3 @@ function preprocessMetadata(metadata: IPublicTypeComponentMetadata): IPublicType
configure: {},
};
}

View File

@ -6,16 +6,22 @@ import {
} from '@alilc/lowcode-types';
import { isNode } from '@alilc/lowcode-utils';
export interface IActiveTracker extends IPublicModelActiveTracker {
track(originalTarget: IPublicTypeActiveTarget | INode): void;
export interface IActiveTracker extends Omit< IPublicModelActiveTracker, 'track' | 'onChange' > {
track(originalTarget: ActiveTarget | INode): void;
onChange(fn: (target: ActiveTarget) => void): () => void;
}
export interface ActiveTarget extends Omit< IPublicTypeActiveTarget, 'node' > {
node: INode;
}
export class ActiveTracker implements IActiveTracker {
private emitter: IEventBus = createModuleEventBus('ActiveTracker');
@obx.ref private _target?: IPublicTypeActiveTarget | INode;
@obx.ref private _target?: ActiveTarget | INode;
track(originalTarget: IPublicTypeActiveTarget | INode) {
track(originalTarget: ActiveTarget | INode) {
let target = originalTarget;
if (isNode(originalTarget)) {
target = { node: originalTarget as INode };
@ -25,11 +31,11 @@ export class ActiveTracker implements IActiveTracker {
}
get currentNode() {
return (this._target as IPublicTypeActiveTarget)?.node;
return (this._target as ActiveTarget)?.node;
}
get detail() {
return (this._target as IPublicTypeActiveTarget)?.detail;
return (this._target as ActiveTarget)?.detail;
}
/**
@ -41,10 +47,10 @@ export class ActiveTracker implements IActiveTracker {
}
get instance() {
return (this._target as IPublicTypeActiveTarget)?.instance;
return (this._target as ActiveTarget)?.instance;
}
onChange(fn: (target: IPublicTypeActiveTarget) => void): () => void {
onChange(fn: (target: ActiveTarget) => void): () => void {
this.emitter.addListener('change', fn);
return () => {
this.emitter.removeListener('change', fn);

View File

@ -1,3 +1,5 @@
import { IPublicModelClipboard } from '@alilc/lowcode-types';
function getDataFromPasteEvent(event: ClipboardEvent) {
const { clipboardData } = event;
if (!clipboardData) {
@ -23,7 +25,13 @@ function getDataFromPasteEvent(event: ClipboardEvent) {
}
}
class Clipboard {
export interface IClipboard extends IPublicModelClipboard {
initCopyPaster(el: HTMLTextAreaElement): void;
injectCopyPaster(document: Document): void;
}
class Clipboard implements IClipboard {
private copyPasters: HTMLTextAreaElement[] = [];
private waitFn?: (data: any, e: ClipboardEvent) => void;
@ -56,7 +64,7 @@ class Clipboard {
}
injectCopyPaster(document: Document) {
if (this.copyPasters.find(x => x.ownerDocument === document)) {
if (this.copyPasters.find((x) => x.ownerDocument === document)) {
return;
}
const copyPaster = document.createElement<'textarea'>('textarea');
@ -69,8 +77,8 @@ class Clipboard {
};
}
setData(data: any) {
const copyPaster = this.copyPasters.find(x => x.ownerDocument);
setData(data: any): void {
const copyPaster = this.copyPasters.find((x) => x.ownerDocument);
if (!copyPaster) {
return;
}
@ -81,12 +89,12 @@ class Clipboard {
copyPaster.blur();
}
waitPasteData(e: KeyboardEvent, cb: (data: any, e: ClipboardEvent) => void) {
const win = e.view;
waitPasteData(keyboardEvent: KeyboardEvent, cb: (data: any, e: ClipboardEvent) => void) {
const win = keyboardEvent.view;
if (!win) {
return;
}
const copyPaster = this.copyPasters.find(cp => cp.ownerDocument === win.document);
const copyPaster = this.copyPasters.find((cp) => cp.ownerDocument === win.document);
if (copyPaster) {
copyPaster.select();
this.waitFn = cb;

View File

@ -12,39 +12,39 @@ import {
IPublicTypePropsTransducer,
IShellModelFactory,
IPublicModelDragObject,
IPublicModelScrollable,
IPublicTypeScrollable,
IPublicModelScroller,
IPublicTypeLocationData,
IPublicEnumTransformStage,
IPublicModelDragon,
IPublicModelActiveTracker,
IPublicModelDropLocation,
} from '@alilc/lowcode-types';
import { megreAssets, IPublicTypeAssetsJson, isNodeSchema, isDragNodeObject, isDragNodeDataObject, isLocationChildrenDetail, Logger } from '@alilc/lowcode-utils';
import { Project } from '../project';
import { Node, DocumentModel, insertChildren, INode } from '../document';
import { ComponentMeta } from '../component-meta';
import { ComponentMeta, IComponentMeta } from '../component-meta';
import { INodeSelector, Component } from '../simulator';
import { Scroller } from './scroller';
import { Dragon, ILocateEvent } from './dragon';
import { ActiveTracker } from './active-tracker';
import { Dragon, IDragon, ILocateEvent } from './dragon';
import { ActiveTracker, IActiveTracker } from './active-tracker';
import { Detecting } from './detecting';
import { DropLocation } from './location';
import { OffsetObserver, createOffsetObserver } from './offset-observer';
import { focusing } from './focusing';
import { SettingTopEntry } from './setting';
import { ISettingTopEntry, SettingTopEntry } from './setting';
import { BemToolsManager } from '../builtin-simulator/bem-tools/manager';
import { ComponentActions } from '../component-actions';
const logger = new Logger({ level: 'warn', bizName: 'designer' });
export interface DesignerProps {
[key: string]: any;
editor: IPublicModelEditor;
shellModelFactory: IShellModelFactory;
className?: string;
style?: object;
defaultSchema?: IPublicTypeProjectSchema;
hotkeys?: object;
viewName?: string;
simulatorProps?: object | ((document: DocumentModel) => object);
simulatorComponent?: ComponentType<any>;
dragGhostComponent?: ComponentType<any>;
@ -54,24 +54,58 @@ export interface DesignerProps {
onMount?: (designer: Designer) => void;
onDragstart?: (e: ILocateEvent) => void;
onDrag?: (e: ILocateEvent) => void;
onDragend?: (e: { dragObject: IPublicModelDragObject; copy: boolean }, loc?: DropLocation) => void;
viewName?: string;
[key: string]: any;
onDragend?: (
e: { dragObject: IPublicModelDragObject; copy: boolean },
loc?: DropLocation,
) => void;
}
export interface IDesigner {
readonly shellModelFactory: IShellModelFactory;
get dragon(): IPublicModelDragon;
get activeTracker(): IPublicModelActiveTracker;
createScroller(scrollable: IPublicModelScrollable): IPublicModelScroller;
get activeTracker(): IActiveTracker;
get componentActions(): ComponentActions;
get editor(): IPublicModelEditor;
get detecting(): Detecting;
createScroller(scrollable: IPublicTypeScrollable): IPublicModelScroller;
/**
* dragon
*/
createLocation(locationData: IPublicTypeLocationData): IPublicModelDropLocation;
get componentsMap(): { [key: string]: IPublicTypeNpmInfo | Component };
loadIncrementalAssets(incrementalAssets: IPublicTypeAssetsJson): Promise<void>;
getComponentMeta(
componentName: string,
generateMetadata?: () => IPublicTypeComponentMetadata | null,
): IComponentMeta;
createComponentMeta(data: IPublicTypeComponentMetadata): IComponentMeta | null;
getComponentMetasMap(): Map<string, IComponentMeta>;
addPropsReducer(reducer: IPublicTypePropsTransducer, stage: IPublicEnumTransformStage): void;
postEvent(event: string, ...args: any[]): void;
transformProps(props: IPublicTypeCompositeObject | IPublicTypePropsList, node: Node, stage: IPublicEnumTransformStage): IPublicTypeCompositeObject | IPublicTypePropsList;
createSettingEntry(nodes: INode[]): ISettingTopEntry;
}
export class Designer implements IDesigner {
dragon: Dragon;
dragon: IDragon;
viewName: string | undefined;
readonly componentActions = new ComponentActions();
@ -87,6 +121,24 @@ export class Designer implements IDesigner {
readonly shellModelFactory: IShellModelFactory;
private _dropLocation?: DropLocation;
private propsReducers = new Map<IPublicEnumTransformStage, IPublicTypePropsTransducer[]>();
private _lostComponentMetasMap = new Map<string, ComponentMeta>();
private props?: DesignerProps;
private oobxList: OffsetObserver[] = [];
@obx.ref private _componentMetasMap = new Map<string, IComponentMeta>();
@obx.ref private _simulatorComponent?: ComponentType<any>;
@obx.ref private _simulatorProps?: object | ((project: Project) => object);
@obx.ref private _suspensed = false;
get currentDocument() {
return this.project.currentDocument;
}
@ -99,8 +151,6 @@ export class Designer implements IDesigner {
return this.currentDocument?.selection;
}
viewName: string | undefined;
constructor(props: DesignerProps) {
makeObservable(this);
const { editor, viewName, shellModelFactory } = props;
@ -199,9 +249,6 @@ export class Designer implements IDesigner {
this.postEvent('init', this);
this.setupSelection();
setupHistory();
// TODO: 先简单实现,后期通过焦点赋值
focusing.focusDesigner = this;
}
setupSelection = () => {
@ -234,8 +281,6 @@ export class Designer implements IDesigner {
this.editor.eventBus.emit(`designer.${event}`, ...args);
}
private _dropLocation?: DropLocation;
get dropLocation() {
return this._dropLocation;
}
@ -266,12 +311,10 @@ export class Designer implements IDesigner {
this._dropLocation = undefined;
}
createScroller(scrollable: IPublicModelScrollable): IPublicModelScroller {
createScroller(scrollable: IPublicTypeScrollable): IPublicModelScroller {
return new Scroller(scrollable);
}
private oobxList: OffsetObserver[] = [];
createOffsetObserver(nodeInstance: INodeSelector): OffsetObserver | null {
const oobx = createOffsetObserver(nodeInstance);
this.clearOobxList();
@ -297,12 +340,13 @@ export class Designer implements IDesigner {
this.oobxList.forEach((item) => item.compute());
}
createSettingEntry(nodes: Node[]) {
createSettingEntry(nodes: INode[]): ISettingTopEntry {
return new SettingTopEntry(this.editor, nodes);
}
/**
*
* @deprecated
*/
getSuitableInsertion(
insertNode?: INode | IPublicTypeNodeSchema | IPublicTypeNodeSchema[],
@ -342,8 +386,6 @@ export class Designer implements IDesigner {
return { target, index };
}
private props?: DesignerProps;
setProps(nextProps: DesignerProps) {
const props = this.props ? { ...this.props, ...nextProps } : nextProps;
if (this.props) {
@ -421,14 +463,10 @@ export class Designer implements IDesigner {
return this.props?.[key];
}
@obx.ref private _simulatorComponent?: ComponentType<any>;
@computed get simulatorComponent(): ComponentType<any> | undefined {
return this._simulatorComponent;
}
@obx.ref private _simulatorProps?: object | ((project: Project) => object);
@computed get simulatorProps(): object {
if (typeof this._simulatorProps === 'function') {
return this._simulatorProps(this.project);
@ -451,8 +489,6 @@ export class Designer implements IDesigner {
};
}
@obx.ref private _suspensed = false;
get suspensed(): boolean {
return this._suspensed;
}
@ -473,16 +509,15 @@ export class Designer implements IDesigner {
this.project.load(schema);
}
@obx.ref private _componentMetasMap = new Map<string, ComponentMeta>();
private _lostComponentMetasMap = new Map<string, ComponentMeta>();
buildComponentMetasMap(metas: IPublicTypeComponentMetadata[]) {
metas.forEach((data) => this.createComponentMeta(data));
}
createComponentMeta(data: IPublicTypeComponentMetadata): ComponentMeta {
createComponentMeta(data: IPublicTypeComponentMetadata): IComponentMeta | null {
const key = data.componentName;
if (!key) {
return null;
}
let meta = this._componentMetasMap.get(key);
if (meta) {
meta.setMetadata(data);
@ -510,7 +545,7 @@ export class Designer implements IDesigner {
getComponentMeta(
componentName: string,
generateMetadata?: () => IPublicTypeComponentMetadata | null,
) {
): IComponentMeta {
if (this._componentMetasMap.has(componentName)) {
return this._componentMetasMap.get(componentName)!;
}
@ -541,7 +576,7 @@ export class Designer implements IDesigner {
if (metaData.devMode === 'lowCode') {
maps[key] = metaData.schema;
} else {
const view = metaData.configure.advanced?.view;
const { view } = config.advanced;
if (view) {
maps[key] = view;
} else {
@ -552,8 +587,6 @@ export class Designer implements IDesigner {
return maps;
}
private propsReducers = new Map<IPublicEnumTransformStage, IPublicTypePropsTransducer[]>();
transformProps(props: IPublicTypeCompositeObject | IPublicTypePropsList, node: Node, stage: IPublicEnumTransformStage) {
if (Array.isArray(props)) {
// current not support, make this future
@ -596,4 +629,4 @@ export class Designer implements IDesigner {
purge() {
// TODO:
}
}
}

View File

@ -1,9 +1,21 @@
import { makeObservable, obx, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core';
import { IPublicModelDetecting, IPublicModelNode, IPublicModelDocumentModel } from '@alilc/lowcode-types';
import { IPublicModelDetecting } from '@alilc/lowcode-types';
import { IDocumentModel } from '../document/document-model';
import { INode } from '../document/node/node';
const DETECTING_CHANGE_EVENT = 'detectingChange';
export interface IDetecting extends IPublicModelDetecting {
export interface IDetecting extends Omit< IPublicModelDetecting<INode>,
'capture' |
'release' |
'leave'
> {
capture(node: INode | null): void;
release(node: INode | null): void;
leave(document: IDocumentModel | undefined): void;
get current(): INode | null;
}
export class Detecting implements IDetecting {
@ -26,7 +38,7 @@ export class Detecting implements IDetecting {
@obx.ref xRayMode = false;
@obx.ref private _current: IPublicModelNode | null = null;
@obx.ref private _current: INode | null = null;
private emitter: IEventBus = createModuleEventBus('Detecting');
@ -38,27 +50,27 @@ export class Detecting implements IDetecting {
return this._current;
}
capture(node: IPublicModelNode | null) {
capture(node: INode | null) {
if (this._current !== node) {
this._current = node;
this.emitter.emit(DETECTING_CHANGE_EVENT, this.current);
}
}
release(node: IPublicModelNode | null) {
release(node: INode | null) {
if (this._current === node) {
this._current = null;
this.emitter.emit(DETECTING_CHANGE_EVENT, this.current);
}
}
leave(document: IPublicModelDocumentModel | undefined) {
leave(document: IDocumentModel | undefined) {
if (this.current && this.current.document === document) {
this._current = null;
}
}
onDetectingChange(fn: (node: IPublicModelNode) => void) {
onDetectingChange(fn: (node: INode) => void) {
this.emitter.on(DETECTING_CHANGE_EVENT, fn);
return () => {
this.emitter.off(DETECTING_CHANGE_EVENT, fn);

View File

@ -39,7 +39,7 @@ export default class DragGhost extends Component<{ designer: Designer }> {
this.y = e.globalY;
if (isSimulatorHost(e.sensor)) {
const container = e.sensor.getDropContainer(e);
if (container?.container.componentMeta.getMetadata().configure.advanced?.isAbsoluteLayoutContainer) {
if (container?.container.componentMeta.advanced.isAbsoluteLayoutContainer) {
this.isAbsoluteLayoutContainer = true;
return;
}

View File

@ -1,8 +0,0 @@
import { Designer } from './designer';
// TODO: use focus-tracker replace
class Focusing {
focusDesigner?: Designer;
}
export const focusing = new Focusing();

View File

@ -7,6 +7,5 @@ export * from './offset-observer';
export * from './scroller';
export * from './setting';
export * from './active-tracker';
export * from './focusing';
export * from '../document';
export * from './clipboard';

View File

@ -1,15 +1,14 @@
import { INode } from '../document';
import { IDocumentModel, INode } from '../document';
import { ILocateEvent } from './dragon';
import {
IPublicModelDocumentModel,
IPublicModelDropLocation,
IPublicTypeLocationDetailType,
IPublicTypeRect,
IPublicTypeLocationDetail,
IPublicTypeLocationData,
IPublicModelLocateEvent,
} from '@alilc/lowcode-types';
export interface Point {
clientX: number;
clientY: number;
@ -99,11 +98,15 @@ function isDocument(elem: any): elem is Document {
export function getWindow(elem: Element | Document): Window {
return (isDocument(elem) ? elem : elem.ownerDocument!).defaultView!;
}
export interface IDropLocation extends IPublicModelDropLocation {
export interface IDropLocation extends Omit< IPublicModelDropLocation, 'target' | 'clone' > {
readonly source: string;
get document(): IPublicModelDocumentModel;
get target(): INode;
get document(): IDocumentModel | null;
clone(event: IPublicModelLocateEvent): IDropLocation;
}
export class DropLocation implements IDropLocation {
@ -115,7 +118,7 @@ export class DropLocation implements IDropLocation {
readonly source: string;
get document(): IPublicModelDocumentModel {
get document(): IDocumentModel | null {
return this.target.document;
}
@ -126,7 +129,7 @@ export class DropLocation implements IDropLocation {
this.event = event;
}
clone(event: ILocateEvent): DropLocation {
clone(event: ILocateEvent): IDropLocation {
return new DropLocation({
target: this.target,
detail: this.detail,
@ -155,7 +158,7 @@ export class DropLocation implements IDropLocation {
if (this.detail.index <= 0) {
return null;
}
return this.target.children.get(this.detail.index - 1);
return this.target.children?.get(this.detail.index - 1);
}
return (this.detail as any)?.near?.node;
}

View File

@ -1,5 +1,5 @@
import { isElement } from '@alilc/lowcode-utils';
import { IPublicModelScrollTarget, IPublicModelScrollable, IPublicModelScroller } from '@alilc/lowcode-types';
import { IPublicModelScrollTarget, IPublicTypeScrollable, IPublicModelScroller } from '@alilc/lowcode-types';
export interface IScrollTarget extends IPublicModelScrollTarget {
}
@ -13,6 +13,14 @@ export class ScrollTarget implements IScrollTarget {
return 'scrollY' in this.target ? this.target.scrollY : this.target.scrollTop;
}
private doc?: HTMLElement;
constructor(private target: Window | Element) {
if (isWindow(target)) {
this.doc = target.document.documentElement;
}
}
scrollTo(options: { left?: number; top?: number }) {
this.target.scrollTo(options);
}
@ -28,14 +36,6 @@ export class ScrollTarget implements IScrollTarget {
get scrollWidth(): number {
return ((this.doc || this.target) as any).scrollWidth;
}
private doc?: HTMLElement;
constructor(private target: Window | Element) {
if (isWindow(target)) {
this.doc = target.document.documentElement;
}
}
}
function isWindow(obj: any): obj is Window {
@ -53,9 +53,9 @@ export interface IScroller extends IPublicModelScroller {
}
export class Scroller implements IScroller {
private pid: number | undefined;
scrollable: IPublicModelScrollable;
scrollable: IPublicTypeScrollable;
constructor(scrollable: IPublicModelScrollable) {
constructor(scrollable: IPublicTypeScrollable) {
this.scrollable = scrollable;
}

View File

@ -1,17 +1,17 @@
import { IPublicModelSettingTarget } from '@alilc/lowcode-types';
import { ComponentMeta } from '../../component-meta';
import { IComponentMeta } from '../../component-meta';
import { Designer } from '../designer';
import { Node } from '../../document';
import { INode } from '../../document';
export interface SettingEntry extends IPublicModelSettingTarget {
readonly nodes: Node[];
readonly componentMeta: ComponentMeta | null;
export interface ISettingEntry extends IPublicModelSettingTarget {
readonly nodes: INode[];
readonly componentMeta: IComponentMeta | null;
readonly designer: Designer;
// 顶端
readonly top: SettingEntry;
readonly top: ISettingEntry;
// 父级
readonly parent: SettingEntry;
readonly parent: ISettingEntry;
get: (propName: string | number) => SettingEntry | null;
get: (propName: string | number) => ISettingEntry | null;
}

View File

@ -1,11 +1,19 @@
import { IPublicTypeTitleContent, IPublicTypeSetterType, IPublicTypeDynamicSetter, IPublicTypeFieldExtraProps, IPublicTypeFieldConfig, IPublicTypeCustomView, IPublicTypeSetValueOptions } from '@alilc/lowcode-types';
import {
IPublicTypeTitleContent,
IPublicTypeSetterType,
IPublicTypeDynamicSetter,
IPublicTypeFieldExtraProps,
IPublicTypeFieldConfig,
IPublicTypeCustomView,
IPublicTypeSetValueOptions,
} from '@alilc/lowcode-types';
import { Transducer } from './utils';
import { SettingPropEntry } from './setting-prop-entry';
import { SettingEntry } from './setting-entry';
import { computed, obx, makeObservable, action, untracked } from '@alilc/lowcode-editor-core';
import { ISettingEntry } from './setting-entry';
import { computed, obx, makeObservable, action, untracked, intl } from '@alilc/lowcode-editor-core';
import { cloneDeep, isCustomView, isDynamicSetter } from '@alilc/lowcode-utils';
function getSettingFieldCollectorKey(parent: SettingEntry, config: IPublicTypeFieldConfig) {
function getSettingFieldCollectorKey(parent: ISettingEntry, config: IPublicTypeFieldConfig) {
let cur = parent;
const path = [config.name];
while (cur !== parent.top) {
@ -17,7 +25,11 @@ function getSettingFieldCollectorKey(parent: SettingEntry, config: IPublicTypeFi
return path.join('.');
}
export class SettingField extends SettingPropEntry implements SettingEntry {
export interface ISettingField extends ISettingEntry {
}
export class SettingField extends SettingPropEntry implements ISettingField {
readonly isSettingField = true;
readonly isRequired: boolean;
@ -26,44 +38,32 @@ export class SettingField extends SettingPropEntry implements SettingEntry {
private _config: IPublicTypeFieldConfig;
private hotValue: any;
parent: ISettingEntry;
extraProps: IPublicTypeFieldExtraProps;
// ==== dynamic properties ====
private _title?: IPublicTypeTitleContent;
get title() {
// FIXME! intl
return this._title || (typeof this.name === 'number' ? `项目 ${this.name}` : this.name);
return (
this._title || (typeof this.name === 'number' ? `${intl('Item')} ${this.name}` : this.name)
);
}
private _setter?: IPublicTypeSetterType | IPublicTypeDynamicSetter;
@computed get setter(): IPublicTypeSetterType | null {
if (!this._setter) {
return null;
}
if (isDynamicSetter(this._setter)) {
return untracked(() => {
const shellThis = this.internalToShellPropEntry();
return this._setter.call(shellThis, shellThis);
});
}
return this._setter;
}
@obx.ref private _expanded = true;
get expanded(): boolean {
return this._expanded;
}
private _items: Array<SettingField | IPublicTypeCustomView> = [];
setExpanded(value: boolean) {
this._expanded = value;
}
parent: SettingEntry;
constructor(parent: SettingEntry, config: IPublicTypeFieldConfig, settingFieldCollector?: (name: string | number, field: SettingField) => void) {
constructor(
parent: ISettingEntry,
config: IPublicTypeFieldConfig,
private settingFieldCollector?: (name: string | number, field: SettingField) => void,
) {
super(parent, config.name, config.type);
makeObservable(this);
const { title, items, setter, extraProps, ...rest } = config;
@ -90,7 +90,26 @@ export class SettingField extends SettingPropEntry implements SettingEntry {
this.transducer = new Transducer(this, { setter });
}
private _items: Array<SettingField | IPublicTypeCustomView> = [];
@computed get setter(): IPublicTypeSetterType | null {
if (!this._setter) {
return null;
}
if (isDynamicSetter(this._setter)) {
return untracked(() => {
const shellThis = this.internalToShellPropEntry();
return this._setter.call(shellThis, shellThis);
});
}
return this._setter;
}
get expanded(): boolean {
return this._expanded;
}
setExpanded(value: boolean) {
this._expanded = value;
}
get items(): Array<SettingField | IPublicTypeCustomView> {
return this._items;
@ -100,7 +119,13 @@ export class SettingField extends SettingPropEntry implements SettingEntry {
return this._config;
}
private initItems(items: Array<IPublicTypeFieldConfig | IPublicTypeCustomView>, settingFieldCollector?: { (name: string | number, field: SettingField): void; (name: string, field: SettingField): void }) {
private initItems(
items: Array<IPublicTypeFieldConfig | IPublicTypeCustomView>,
settingFieldCollector?: {
(name: string | number, field: SettingField): void;
(name: string, field: SettingField): void;
},
) {
this._items = items.map((item) => {
if (isCustomView(item)) {
return item;
@ -110,13 +135,14 @@ export class SettingField extends SettingPropEntry implements SettingEntry {
}
private disposeItems() {
this._items.forEach(item => isSettingField(item) && item.purge());
this._items.forEach((item) => isSettingField(item) && item.purge());
this._items = [];
}
// 创建子配置项,通常用于 object/array 类型数据
createField(config: IPublicTypeFieldConfig): SettingField {
return new SettingField(this, config);
this.settingFieldCollector?.(getSettingFieldCollectorKey(this.parent, config), this);
return new SettingField(this, config, this.settingFieldCollector);
}
purge() {
@ -125,15 +151,19 @@ export class SettingField extends SettingPropEntry implements SettingEntry {
// ======= compatibles for vision ======
getConfig<K extends keyof IPublicTypeFieldConfig>(configName?: K): IPublicTypeFieldConfig[K] | IPublicTypeFieldConfig {
getConfig<K extends keyof IPublicTypeFieldConfig>(
configName?: K,
): IPublicTypeFieldConfig[K] | IPublicTypeFieldConfig {
if (configName) {
return this.config[configName];
}
return this._config;
}
getItems(filter?: (item: SettingField | IPublicTypeCustomView) => boolean): Array<SettingField | IPublicTypeCustomView> {
return this._items.filter(item => {
getItems(
filter?: (item: SettingField | IPublicTypeCustomView) => boolean,
): Array<SettingField | IPublicTypeCustomView> {
return this._items.filter((item) => {
if (filter) {
return filter(item);
}
@ -141,10 +171,13 @@ export class SettingField extends SettingPropEntry implements SettingEntry {
});
}
private hotValue: any;
@action
setValue(val: any, isHotValue?: boolean, force?: boolean, extraOptions?: IPublicTypeSetValueOptions) {
setValue(
val: any,
isHotValue?: boolean,
force?: boolean,
extraOptions?: IPublicTypeSetValueOptions,
) {
if (isHotValue) {
this.setHotValue(val, extraOptions);
return;
@ -189,11 +222,16 @@ export class SettingField extends SettingPropEntry implements SettingEntry {
}
if (this.isUseVariable()) {
const oldValue = this.getValue();
this.setValue({
type: 'JSExpression',
value: oldValue.value,
mock: value,
}, false, false, options);
this.setValue(
{
type: 'JSExpression',
value: oldValue.value,
mock: value,
},
false,
false,
options,
);
} else {
this.setValue(value, false, false, options);
}
@ -210,6 +248,7 @@ export class SettingField extends SettingPropEntry implements SettingEntry {
return this.designer.autorun(action, true);
}
}
/**
* @deprecated use same function from '@alilc/lowcode-utils' instead
*/

View File

@ -1,13 +1,14 @@
import { obx, computed, makeObservable, runInAction, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core';
import { GlobalEvent, IPublicModelEditor, IPublicTypeSetValueOptions } from '@alilc/lowcode-types';
import { uniqueId, isJSExpression, isSettingField } from '@alilc/lowcode-utils';
import { SettingEntry } from './setting-entry';
import { Node } from '../../document';
import { ComponentMeta } from '../../component-meta';
import { Designer } from '../designer';
import { Setters } from '@alilc/lowcode-shell';
import { ISettingEntry } from './setting-entry';
import { INode } from '../../document';
import { IComponentMeta } from '../../component-meta';
import { Designer } from '../designer';
import { ISettingField } from './setting-field';
export class SettingPropEntry implements SettingEntry {
export class SettingPropEntry implements ISettingEntry {
// === static properties ===
readonly editor: IPublicModelEditor;
@ -19,13 +20,13 @@ export class SettingPropEntry implements SettingEntry {
readonly setters: Setters;
readonly nodes: Node[];
readonly nodes: INode[];
readonly componentMeta: ComponentMeta | null;
readonly componentMeta: IComponentMeta | null;
readonly designer: Designer;
readonly top: SettingEntry;
readonly top: ISettingEntry;
readonly isGroup: boolean;
@ -52,7 +53,7 @@ export class SettingPropEntry implements SettingEntry {
extraProps: any = {};
constructor(readonly parent: SettingEntry, name: string | number, type?: 'field' | 'group') {
constructor(readonly parent: ISettingEntry | ISettingField, name: string | number, type?: 'field' | 'group') {
makeObservable(this);
if (type == null) {
const c = typeof name === 'string' ? name.slice(0, 1) : '';

View File

@ -1,22 +1,26 @@
import { IPublicTypeCustomView, IPublicModelEditor } from '@alilc/lowcode-types';
import { isCustomView } from '@alilc/lowcode-utils';
import { computed, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core';
import { SettingEntry } from './setting-entry';
import { ISettingEntry } from './setting-entry';
import { SettingField } from './setting-field';
import { SettingPropEntry } from './setting-prop-entry';
import { Node } from '../../document';
import { INode } from '../../document';
import { ComponentMeta } from '../../component-meta';
import { Designer } from '../designer';
import { IDesigner } from '../designer';
import { Setters } from '@alilc/lowcode-shell';
function generateSessionId(nodes: Node[]) {
function generateSessionId(nodes: INode[]) {
return nodes
.map((node) => node.id)
.sort()
.join(',');
}
export class SettingTopEntry implements SettingEntry {
export interface ISettingTopEntry extends ISettingEntry {
purge(): void;
}
export class SettingTopEntry implements ISettingTopEntry {
private emitter: IEventBus = createModuleEventBus('SettingTopEntry');
private _items: Array<SettingField | IPublicTypeCustomView> = [];
@ -68,21 +72,21 @@ export class SettingTopEntry implements SettingEntry {
readonly id: string;
readonly first: Node;
readonly first: INode;
readonly designer: Designer;
readonly designer: IDesigner | undefined;
readonly setters: Setters;
disposeFunctions: any[] = [];
constructor(readonly editor: IPublicModelEditor, readonly nodes: Node[]) {
constructor(readonly editor: IPublicModelEditor, readonly nodes: INode[]) {
if (!Array.isArray(nodes) || nodes.length < 1) {
throw new ReferenceError('nodes should not be empty');
}
this.id = generateSessionId(nodes);
this.first = nodes[0];
this.designer = this.first.document.designer;
this.designer = this.first.document?.designer;
this.setters = editor.get('setters') as Setters;
// setups
@ -229,7 +233,6 @@ export class SettingTopEntry implements SettingEntry {
this.disposeFunctions = [];
}
getProp(propName: string | number) {
return this.get(propName);
}

View File

@ -1,4 +1,13 @@
import { makeObservable, obx, engineConfig, action, runWithGlobalEventOff, wrapWithEventSwitch, createModuleEventBus, IEventBus } from '@alilc/lowcode-editor-core';
import {
makeObservable,
obx,
engineConfig,
action,
runWithGlobalEventOff,
wrapWithEventSwitch,
createModuleEventBus,
IEventBus,
} from '@alilc/lowcode-editor-core';
import {
IPublicTypeNodeData,
IPublicTypeNodeSchema,
@ -8,24 +17,32 @@ import {
IPublicTypeDragNodeObject,
IPublicTypeDragNodeDataObject,
IPublicModelDocumentModel,
IPublicModelSelection,
IPublicModelHistory,
IPublicModelModalNodesManager,
IPublicModelNode,
IPublicApiProject,
IPublicModelDropLocation,
IPublicEnumTransformStage,
IPublicTypeOnChangeOptions,
IPublicTypeDisposable,
} from '@alilc/lowcode-types';
import { Project } from '../project';
import {
IDropLocation,
} from '@alilc/lowcode-designer';
import {
uniqueId,
isPlainObject,
compatStage,
isJSExpression,
isDOMText,
isNodeSchema,
isDragNodeObject,
isDragNodeDataObject,
isNode,
} from '@alilc/lowcode-utils';
import { IProject, Project } from '../project';
import { ISimulatorHost } from '../simulator';
import { ComponentMeta } from '../component-meta';
import { IDropLocation, Designer } from '../designer';
import { Node, insertChildren, insertChild, isNode, RootNode, INode } from './node/node';
import { Selection } from './selection';
import { IComponentMeta } from '../component-meta';
import { IDesigner, IHistory } from '../designer';
import { insertChildren, insertChild, RootNode, INode } from './node/node';
import { Selection, ISelection } from './selection';
import { History } from './history';
import { ModalNodesManager } from './node';
import { uniqueId, isPlainObject, compatStage, isJSExpression, isDOMText, isNodeSchema, isDragNodeObject, isDragNodeDataObject } from '@alilc/lowcode-utils';
import { IModalNodesManager, ModalNodesManager, Node } from './node';
import { EDITOR_EVENT } from '../types';
export type GetDataType<T, NodeType> = T extends undefined
@ -35,17 +52,83 @@ export type GetDataType<T, NodeType> = T extends undefined
? R
: any
: T;
export interface IDocumentModel extends IPublicModelDocumentModel {
readonly designer: Designer;
export interface IDocumentModel extends Omit< IPublicModelDocumentModel<
ISelection,
IHistory,
INode | RootNode,
IDropLocation,
IModalNodesManager,
IProject
>,
'detecting' |
'checkNesting' |
'getNodeById' |
// 以下属性在内部的 document 中不存在
'exportSchema' |
'importSchema' |
'onAddNode' |
'onRemoveNode' |
'onChangeDetecting' |
'onChangeSelection' |
'onMountNode' |
'onChangeNodeProp' |
'onImportSchema' |
'isDetectingNode' |
'onFocusNodeChanged' |
'onDropLocationChanged'
> {
readonly designer: IDesigner;
get rootNode(): INode | null;
get simulator(): ISimulatorHost | null;
get active(): boolean;
get nodesMap(): Map<string, INode>;
/**
* id
*/
getNode(id: string): INode | null;
getHistory(): IHistory;
checkNesting(
dropTarget: INode,
dragObject: IPublicTypeDragNodeObject | IPublicTypeNodeSchema | INode | IPublicTypeDragNodeDataObject,
): boolean;
getNodeCount(): number;
nextId(possibleId: string | undefined): string;
import(schema: IPublicTypeRootSchema, checkId?: boolean): void;
export(stage: IPublicEnumTransformStage): IPublicTypeRootSchema | undefined;
onNodeCreate(func: (node: INode) => void): IPublicTypeDisposable;
onNodeDestroy(func: (node: INode) => void): IPublicTypeDisposable;
onChangeNodeVisible(fn: (node: INode, visible: boolean) => void): IPublicTypeDisposable;
addWillPurge(node: INode): void;
removeWillPurge(node: INode): void;
getComponentMeta(componentName: string): IComponentMeta;
insertNodes(parent: INode, thing: INode[] | IPublicTypeNodeData[], at?: number | null, copy?: boolean): INode[];
}
export class DocumentModel implements IDocumentModel {
/**
* Page/Component/Block
*/
rootNode: RootNode | null;
rootNode: INode | null;
/**
*
@ -55,25 +138,25 @@ export class DocumentModel implements IDocumentModel {
/**
*
*/
readonly selection: IPublicModelSelection = new Selection(this);
readonly selection: ISelection = new Selection(this);
/**
*
*/
readonly history: IPublicModelHistory;
readonly history: IHistory;
/**
*
*/
readonly modalNodesManager: IPublicModelModalNodesManager;
modalNodesManager: IModalNodesManager;
private _nodesMap = new Map<string, IPublicModelNode>();
private _nodesMap = new Map<string, INode>();
readonly project: IPublicApiProject;
readonly project: IProject;
readonly designer: Designer;
readonly designer: IDesigner;
@obx.shallow private nodes = new Set<IPublicModelNode>();
@obx.shallow private nodes = new Set<INode>();
private seqId = 0;
@ -93,7 +176,7 @@ export class DocumentModel implements IDocumentModel {
return this.project.simulator;
}
get nodesMap(): Map<string, Node> {
get nodesMap(): Map<string, INode> {
return this._nodesMap;
}
@ -105,7 +188,7 @@ export class DocumentModel implements IDocumentModel {
this.rootNode?.getExtraProp('fileName', true)?.setValue(fileName);
}
get focusNode() {
get focusNode(): INode | null {
if (this._drillDownNode) {
return this._drillDownNode;
}
@ -116,11 +199,7 @@ export class DocumentModel implements IDocumentModel {
return this.rootNode;
}
@obx.ref private _drillDownNode: Node | null = null;
drillDown(node: Node | null) {
this._drillDownNode = node;
}
@obx.ref private _drillDownNode: INode | null = null;
private _modalNode?: INode;
@ -128,57 +207,7 @@ export class DocumentModel implements IDocumentModel {
private inited = false;
constructor(project: Project, schema?: IPublicTypeRootSchema) {
makeObservable(this);
this.project = project;
this.designer = this.project?.designer;
this.emitter = createModuleEventBus('DocumentModel');
if (!schema) {
this._blank = true;
}
// 兼容 vision
this.id = project.getSchema()?.id || this.id;
this.rootNode = this.createNode<RootNode>(
schema || {
componentName: 'Page',
id: 'root',
fileName: '',
},
);
this.history = new History(
() => this.export(IPublicEnumTransformStage.Serilize),
(schema) => {
this.import(schema as IPublicTypeRootSchema, true);
this.simulator?.rerender();
},
);
this.setupListenActiveNodes();
this.modalNodesManager = new ModalNodesManager(this);
this.inited = true;
}
onChangeNodeVisible(fn: (node: IPublicModelNode, visible: boolean) => void): () => void {
this.designer.editor?.eventBus.on(EDITOR_EVENT.NODE_CHILDREN_CHANGE, fn);
return () => {
this.designer.editor?.eventBus.off(EDITOR_EVENT.NODE_CHILDREN_CHANGE, fn);
};
}
onChangeNodeChildren(fn: (info: IPublicTypeOnChangeOptions) => void): () => void {
this.designer.editor?.eventBus.on(EDITOR_EVENT.NODE_VISIBLE_CHANGE, fn);
return () => {
this.designer.editor?.eventBus.off(EDITOR_EVENT.NODE_VISIBLE_CHANGE, fn);
};
}
@obx.shallow private willPurgeSpace: Node[] = [];
@obx.shallow private willPurgeSpace: INode[] = [];
get modalNode() {
return this._modalNode;
@ -188,159 +217,11 @@ export class DocumentModel implements IDocumentModel {
return this.modalNode || this.focusNode;
}
addWillPurge(node: Node) {
this.willPurgeSpace.push(node);
}
removeWillPurge(node: Node) {
const i = this.willPurgeSpace.indexOf(node);
if (i > -1) {
this.willPurgeSpace.splice(i, 1);
}
}
isBlank() {
return this._blank && !this.isModified();
}
/**
* id
*/
nextId(possibleId: string | undefined) {
let id = possibleId;
while (!id || this.nodesMap.get(id)) {
id = `node_${(String(this.id).slice(-10) + (++this.seqId).toString(36)).toLocaleLowerCase()}`;
}
return id;
}
/**
* id
*/
getNode(id: string): Node | null {
return this._nodesMap.get(id) || null;
}
/**
* id
*/
getNodeCount(): number {
return this._nodesMap?.size;
}
/**
*
*/
hasNode(id: string): boolean {
const node = this.getNode(id);
return node ? !node.isPurged : false;
}
@obx.shallow private activeNodes?: Node[];
/**
* schema
*/
@action
createNode<T extends Node = Node, C = undefined>(data: GetDataType<C, T>, checkId: boolean = true): T {
let schema: any;
if (isDOMText(data) || isJSExpression(data)) {
schema = {
componentName: 'Leaf',
children: data,
};
} else {
schema = data;
}
let node: Node | null = null;
if (this.hasNode(schema?.id)) {
schema.id = null;
}
/* istanbul ignore next */
if (schema.id) {
node = this.getNode(schema.id);
// TODO: 底下这几段代码似乎永远都进不去
if (node && node.componentName === schema.componentName) {
if (node.parent) {
node.internalSetParent(null, false);
// will move to another position
// todo: this.activeNodes?.push(node);
}
node.import(schema, true);
} else if (node) {
node = null;
}
}
if (!node) {
node = new Node(this, schema, { checkId });
// will add
// todo: this.activeNodes?.push(node);
}
this._nodesMap.set(node.id, node);
this.nodes.add(node);
this.emitter.emit('nodecreate', node);
return node as any;
}
public destroyNode(node: Node) {
this.emitter.emit('nodedestroy', node);
}
/**
*
*/
insertNode(parent: INode, thing: Node | IPublicTypeNodeData, at?: number | null, copy?: boolean): Node {
return insertChild(parent, thing, at, copy);
}
/**
*
*/
insertNodes(parent: INode, thing: Node[] | IPublicTypeNodeData[], at?: number | null, copy?: boolean) {
return insertChildren(parent, thing, at, copy);
}
/**
*
*/
removeNode(idOrNode: string | Node) {
let id: string;
let node: Node | null;
if (typeof idOrNode === 'string') {
id = idOrNode;
node = this.getNode(id);
} else if (idOrNode.id) {
id = idOrNode.id;
node = this.getNode(id);
}
if (!node) {
return;
}
this.internalRemoveAndPurgeNode(node, true);
}
/**
*
*/
internalRemoveAndPurgeNode(node: Node, useMutator = false) {
if (!this.nodes.has(node)) {
return;
}
node.remove(useMutator);
}
unlinkNode(node: Node) {
this.nodes.delete(node);
this._nodesMap.delete(node.id);
}
@obx.shallow private activeNodes?: INode[];
@obx.ref private _dropLocation: IDropLocation | null = null;
set dropLocation(loc: IPublicModelDropLocation | null) {
set dropLocation(loc: IDropLocation | null) {
this._dropLocation = loc;
// pub event
this.designer.editor.eventBus.emit(
@ -356,28 +237,6 @@ export class DocumentModel implements IDocumentModel {
return this._dropLocation;
}
/**
*
*/
wrapWith(schema: IPublicTypeNodeSchema): Node | null {
const nodes = this.selection.getTopNodes();
if (nodes.length < 1) {
return null;
}
const wrapper = this.createNode(schema);
if (wrapper.isParental()) {
const first = nodes[0];
// TODO: check nesting rules x 2
insertChild(first.parent!, wrapper, first.index);
insertChildren(wrapper, nodes);
this.selection.select(wrapper.id);
return wrapper;
}
this.removeNode(wrapper);
return null;
}
/**
* schema
*/
@ -385,70 +244,6 @@ export class DocumentModel implements IDocumentModel {
return this.rootNode?.schema as any;
}
@action
import(schema: IPublicTypeRootSchema, checkId = false) {
const drillDownNodeId = this._drillDownNode?.id;
runWithGlobalEventOff(() => {
// TODO: 暂时用饱和式删除,原因是 Slot 节点并不是树节点,无法正常递归删除
this.nodes.forEach(node => {
if (node.isRoot()) return;
this.internalRemoveAndPurgeNode(node, true);
});
this.rootNode?.import(schema as any, checkId);
this.modalNodesManager = new ModalNodesManager(this);
// todo: select added and active track added
if (drillDownNodeId) {
this.drillDown(this.getNode(drillDownNodeId));
}
});
}
export(stage: IPublicEnumTransformStage = IPublicEnumTransformStage.Serilize) {
stage = compatStage(stage);
// 置顶只作用于 Page 的第一级子节点,目前还用不到里层的置顶;如果后面有需要可以考虑将这段写到 node-children 中的 export
const currentSchema = this.rootNode?.export(stage);
if (Array.isArray(currentSchema?.children) && currentSchema?.children.length > 0) {
const FixedTopNodeIndex = currentSchema.children
.filter(i => isPlainObject(i))
.findIndex((i => (i as IPublicTypeNodeSchema).props?.__isTopFixed__));
if (FixedTopNodeIndex > 0) {
const FixedTopNode = currentSchema.children.splice(FixedTopNodeIndex, 1);
currentSchema.children.unshift(FixedTopNode[0]);
}
}
return currentSchema;
}
/**
*
*/
getNodeSchema(id: string): IPublicTypeNodeData | null {
const node = this.getNode(id);
if (node) {
return node.schema;
}
return null;
}
/**
*
*/
isModified() {
return this.history.isSavePoint();
}
// FIXME: does needed?
getComponent(componentName: string): any {
return this.simulator!.getComponent(componentName);
}
getComponentMeta(componentName: string): ComponentMeta {
return this.designer.getComponentMeta(
componentName,
() => this.simulator?.generateComponentMetadata(componentName) || null,
);
}
@obx.ref private _opened = false;
@obx.ref private _suspensed = false;
@ -481,6 +276,298 @@ export class DocumentModel implements IDocumentModel {
return this._opened;
}
get root() {
return this.rootNode;
}
constructor(project: Project, schema?: IPublicTypeRootSchema) {
makeObservable(this);
this.project = project;
this.designer = this.project?.designer;
this.emitter = createModuleEventBus('DocumentModel');
if (!schema) {
this._blank = true;
}
// 兼容 vision
this.id = project.getSchema()?.id || this.id;
this.rootNode = this.createNode(
schema || {
componentName: 'Page',
id: 'root',
fileName: '',
},
);
this.history = new History(
() => this.export(IPublicEnumTransformStage.Serilize),
(schema) => {
this.import(schema as IPublicTypeRootSchema, true);
this.simulator?.rerender();
},
);
this.setupListenActiveNodes();
this.modalNodesManager = new ModalNodesManager(this);
this.inited = true;
}
drillDown(node: INode | null) {
this._drillDownNode = node;
}
onChangeNodeVisible(fn: (node: INode, visible: boolean) => void): IPublicTypeDisposable {
this.designer.editor?.eventBus.on(EDITOR_EVENT.NODE_CHILDREN_CHANGE, fn);
return () => {
this.designer.editor?.eventBus.off(EDITOR_EVENT.NODE_CHILDREN_CHANGE, fn);
};
}
onChangeNodeChildren(fn: (info: IPublicTypeOnChangeOptions<INode>) => void): IPublicTypeDisposable {
this.designer.editor?.eventBus.on(EDITOR_EVENT.NODE_VISIBLE_CHANGE, fn);
return () => {
this.designer.editor?.eventBus.off(EDITOR_EVENT.NODE_VISIBLE_CHANGE, fn);
};
}
addWillPurge(node: INode) {
this.willPurgeSpace.push(node);
}
removeWillPurge(node: INode) {
const i = this.willPurgeSpace.indexOf(node);
if (i > -1) {
this.willPurgeSpace.splice(i, 1);
}
}
isBlank() {
return this._blank && !this.isModified();
}
/**
* id
*/
nextId(possibleId: string | undefined): string {
let id = possibleId;
while (!id || this.nodesMap.get(id)) {
id = `node_${(String(this.id).slice(-10) + (++this.seqId).toString(36)).toLocaleLowerCase()}`;
}
return id;
}
/**
* id
*/
getNode(id: string): INode | null {
return this._nodesMap.get(id) || null;
}
/**
* id
*/
getNodeCount(): number {
return this._nodesMap?.size;
}
/**
*
*/
hasNode(id: string): boolean {
const node = this.getNode(id);
return node ? !node.isPurged : false;
}
/**
* schema
*/
@action
createNode<T extends INode = INode, C = undefined>(data: GetDataType<C, T>): T {
let schema: any;
if (isDOMText(data) || isJSExpression(data)) {
schema = {
componentName: 'Leaf',
children: data,
};
} else {
schema = data;
}
let node: INode | null = null;
if (this.hasNode(schema?.id)) {
schema.id = null;
}
/* istanbul ignore next */
if (schema.id) {
node = this.getNode(schema.id);
// TODO: 底下这几段代码似乎永远都进不去
if (node && node.componentName === schema.componentName) {
if (node.parent) {
node.internalSetParent(null, false);
// will move to another position
// todo: this.activeNodes?.push(node);
}
node.import(schema, true);
} else if (node) {
node = null;
}
}
if (!node) {
node = new Node(this, schema);
// will add
// todo: this.activeNodes?.push(node);
}
this._nodesMap.set(node.id, node);
this.nodes.add(node);
this.emitter.emit('nodecreate', node);
return node as any;
}
public destroyNode(node: INode) {
this.emitter.emit('nodedestroy', node);
}
/**
*
*/
insertNode(parent: INode, thing: INode | IPublicTypeNodeData, at?: number | null, copy?: boolean): INode | null {
return insertChild(parent, thing, at, copy);
}
/**
*
*/
insertNodes(parent: INode, thing: INode[] | IPublicTypeNodeData[], at?: number | null, copy?: boolean) {
return insertChildren(parent, thing, at, copy);
}
/**
*
*/
removeNode(idOrNode: string | INode) {
let id: string;
let node: INode | null = null;
if (typeof idOrNode === 'string') {
id = idOrNode;
node = this.getNode(id);
} else if (idOrNode.id) {
id = idOrNode.id;
node = this.getNode(id);
}
if (!node) {
return;
}
this.internalRemoveAndPurgeNode(node, true);
}
/**
*
*/
internalRemoveAndPurgeNode(node: INode, useMutator = false) {
if (!this.nodes.has(node)) {
return;
}
node.remove(useMutator);
}
unlinkNode(node: INode) {
this.nodes.delete(node);
this._nodesMap.delete(node.id);
}
/**
*
*/
wrapWith(schema: IPublicTypeNodeSchema): INode | null {
const nodes = this.selection.getTopNodes();
if (nodes.length < 1) {
return null;
}
const wrapper = this.createNode(schema);
if (wrapper.isParental()) {
const first = nodes[0];
// TODO: check nesting rules x 2
insertChild(first.parent!, wrapper, first.index);
insertChildren(wrapper, nodes);
this.selection.select(wrapper.id);
return wrapper;
}
this.removeNode(wrapper);
return null;
}
@action
import(schema: IPublicTypeRootSchema, checkId = false) {
const drillDownNodeId = this._drillDownNode?.id;
runWithGlobalEventOff(() => {
// TODO: 暂时用饱和式删除,原因是 Slot 节点并不是树节点,无法正常递归删除
this.nodes.forEach(node => {
if (node.isRoot()) return;
this.internalRemoveAndPurgeNode(node, true);
});
this.rootNode?.import(schema as any, checkId);
this.modalNodesManager = new ModalNodesManager(this);
// todo: select added and active track added
if (drillDownNodeId) {
this.drillDown(this.getNode(drillDownNodeId));
}
});
}
export(stage: IPublicEnumTransformStage = IPublicEnumTransformStage.Serilize): IPublicTypeRootSchema | undefined {
stage = compatStage(stage);
// 置顶只作用于 Page 的第一级子节点,目前还用不到里层的置顶;如果后面有需要可以考虑将这段写到 node-children 中的 export
const currentSchema = this.rootNode?.export<IPublicTypeRootSchema>(stage);
if (Array.isArray(currentSchema?.children) && currentSchema?.children?.length && currentSchema?.children?.length > 0) {
const FixedTopNodeIndex = currentSchema?.children
.filter(i => isPlainObject(i))
.findIndex((i => (i as IPublicTypeNodeSchema).props?.__isTopFixed__));
if (FixedTopNodeIndex > 0) {
const FixedTopNode = currentSchema?.children.splice(FixedTopNodeIndex, 1);
currentSchema?.children.unshift(FixedTopNode[0]);
}
}
return currentSchema;
}
/**
*
*/
getNodeSchema(id: string): IPublicTypeNodeData | null {
const node = this.getNode(id);
if (node) {
return node.schema;
}
return null;
}
/**
*
*/
isModified() {
return this.history.isSavePoint();
}
// FIXME: does needed?
getComponent(componentName: string): any {
return this.simulator!.getComponent(componentName);
}
getComponentMeta(componentName: string): IComponentMeta {
return this.designer.getComponentMeta(
componentName,
() => this.simulator?.generateComponentMetadata(componentName) || null,
);
}
/**
*
* tab
@ -545,11 +632,14 @@ export class DocumentModel implements IDocumentModel {
this.rootNode = null;
}
checkNesting(dropTarget: INode, dragObject: IPublicTypeDragNodeObject | IPublicTypeNodeSchema | Node | IPublicTypeDragNodeDataObject): boolean {
let items: Array<Node | IPublicTypeNodeSchema>;
checkNesting(
dropTarget: INode,
dragObject: IPublicTypeDragNodeObject | IPublicTypeNodeSchema | INode | IPublicTypeDragNodeDataObject,
): boolean {
let items: Array<INode | IPublicTypeNodeSchema>;
if (isDragNodeDataObject(dragObject)) {
items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];
} else if (isDragNodeObject(dragObject)) {
} else if (isDragNodeObject<INode>(dragObject)) {
items = dragObject.nodes;
} else if (isNode(dragObject) || isNodeSchema(dragObject)) {
items = [dragObject];
@ -566,11 +656,13 @@ export class DocumentModel implements IDocumentModel {
* Use checkNesting method instead.
*/
checkDropTarget(dropTarget: INode, dragObject: IPublicTypeDragNodeObject | IPublicTypeDragNodeDataObject): boolean {
let items: Array<Node | IPublicTypeNodeSchema>;
let items: Array<INode | IPublicTypeNodeSchema>;
if (isDragNodeDataObject(dragObject)) {
items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];
} else {
} else if (isDragNodeObject<INode>(dragObject)) {
items = dragObject.nodes;
} else {
return false;
}
return items.every((item) => this.checkNestingUp(dropTarget, item));
}
@ -578,7 +670,7 @@ export class DocumentModel implements IDocumentModel {
/**
* parentWhitelist
*/
checkNestingUp(parent: INode, obj: IPublicTypeNodeSchema | Node): boolean {
checkNestingUp(parent: INode, obj: IPublicTypeNodeSchema | INode): boolean {
if (isNode(obj) || isNodeSchema(obj)) {
const config = isNode(obj) ? obj.componentMeta : this.getComponentMeta(obj.componentName);
if (config) {
@ -592,7 +684,7 @@ export class DocumentModel implements IDocumentModel {
/**
* childWhitelist
*/
checkNestingDown(parent: INode, obj: IPublicTypeNodeSchema | Node): boolean {
checkNestingDown(parent: INode, obj: IPublicTypeNodeSchema | INode): boolean {
const config = parent.componentMeta;
return config.checkNestingDown(parent, obj);
}
@ -613,14 +705,10 @@ export class DocumentModel implements IDocumentModel {
return data;
}
getHistory(): History {
getHistory(): IHistory {
return this.history;
}
get root() {
return this.rootNode;
}
/**
* @deprecated
*/
@ -637,7 +725,9 @@ export class DocumentModel implements IDocumentModel {
*/
/* istanbul ignore next */
exportAddonData() {
const addons = {};
const addons: {
[key: string]: any;
} = {};
this._addons.forEach((addon) => {
const data = addon.exportData();
if (data === null) {
@ -744,7 +834,7 @@ export class DocumentModel implements IDocumentModel {
}));
}
onNodeCreate(func: (node: Node) => void) {
onNodeCreate(func: (node: INode) => void) {
const wrappedFunc = wrapWithEventSwitch(func);
this.emitter.on('nodecreate', wrappedFunc);
return () => {
@ -752,7 +842,7 @@ export class DocumentModel implements IDocumentModel {
};
}
onNodeDestroy(func: (node: Node) => void) {
onNodeDestroy(func: (node: INode) => void) {
const wrappedFunc = wrapWithEventSwitch(func);
this.emitter.on('nodedestroy', wrappedFunc);
return () => {
@ -777,7 +867,7 @@ export class DocumentModel implements IDocumentModel {
onReady(fn: Function) {
this.designer.editor.eventBus.on('document-open', fn);
return () => {
this.designer.editor.removeListener('document-open', fn);
this.designer.editor.eventBus.off('document-open', fn);
};
}

View File

@ -1,5 +1,8 @@
import { reaction, untracked, globalContext, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core';
import { IPublicTypeNodeSchema, IPublicModelHistory } from '@alilc/lowcode-types';
import { Logger } from '@alilc/lowcode-utils';
const logger = new Logger({ level: 'warn', bizName: 'history' });
export interface Serialization<K = IPublicTypeNodeSchema, T = string> {
serialize(data: K): T;
@ -30,11 +33,15 @@ export class History<T = IPublicTypeNodeSchema> implements IHistory {
},
};
setSerialization(serialization: Serialization<T, string>) {
this.currentSerialization = serialization;
get hotData() {
return this.session.data;
}
constructor(dataFn: () => T | null, private redoer: (data: T) => void, private timeGap: number = 1000) {
constructor(
dataFn: () => T | null,
private redoer: (data: T) => void,
private timeGap: number = 1000,
) {
this.session = new Session(0, null, this.timeGap);
this.records = [this.session];
@ -68,8 +75,8 @@ export class History<T = IPublicTypeNodeSchema> implements IHistory {
}, { fireImmediately: true });
}
get hotData() {
return this.session.data;
setSerialization(serialization: Serialization<T, string>) {
this.currentSerialization = serialization;
}
isSavePoint(): boolean {
@ -84,16 +91,18 @@ export class History<T = IPublicTypeNodeSchema> implements IHistory {
this.asleep = false;
}
go(cursor: number) {
go(originalCursor: number) {
this.session.end();
const currentCursor = this.session.cursor;
let cursor = originalCursor;
cursor = +cursor;
if (cursor < 0) {
cursor = 0;
} else if (cursor >= this.records.length) {
cursor = this.records.length - 1;
}
const currentCursor = this.session.cursor;
if (cursor === currentCursor) {
return;
}
@ -106,7 +115,7 @@ export class History<T = IPublicTypeNodeSchema> implements IHistory {
this.redoer(this.currentSerialization.unserialize(hotData));
this.emitter.emit('cursor', hotData);
} catch (e) /* istanbul ignore next */ {
console.error(e);
logger.error(e);
}
this.wakeup();
@ -174,6 +183,7 @@ export class History<T = IPublicTypeNodeSchema> implements IHistory {
}
return state;
}
/**
* state
* @param func
@ -183,7 +193,7 @@ export class History<T = IPublicTypeNodeSchema> implements IHistory {
return this.onStateChange(func);
}
onStateChange(func: () => any) {
onStateChange(func: () => any): () => void {
this.emitter.on('statechange', func);
return () => {
this.emitter.removeListener('statechange', func);
@ -198,7 +208,8 @@ export class History<T = IPublicTypeNodeSchema> implements IHistory {
onChangeCursor(func: () => any): () => void {
return this.onCursor(func);
}
onCursor(func: () => any) {
onCursor(func: () => any): () => void {
this.emitter.on('cursor', func);
return () => {
this.emitter.removeListener('cursor', func);
@ -209,6 +220,7 @@ export class History<T = IPublicTypeNodeSchema> implements IHistory {
this.emitter.removeAllListeners();
this.records = [];
}
/**
*
* @deprecated

View File

@ -1,18 +1,30 @@
import { obx, computed, makeObservable } from '@alilc/lowcode-editor-core';
import { uniqueId } from '@alilc/lowcode-utils';
import { IPublicTypeTitleContent, IPublicModelExclusiveGroup } from '@alilc/lowcode-types';
import { Node } from './node';
import { INode } from './node';
import { intl } from '../../locale';
export interface IExclusiveGroup extends IPublicModelExclusiveGroup<INode> {
readonly name: string;
remove(node: INode): void;
add(node: INode): void;
isVisible(node: INode): boolean;
}
// modals assoc x-hide value, initial: check is Modal, yes will put it in modals, cross levels
// if-else-if assoc conditionGroup value, should be the same level,
// and siblings, need renderEngine support
export class ExclusiveGroup implements IPublicModelExclusiveGroup {
export class ExclusiveGroup implements IExclusiveGroup {
readonly isExclusiveGroup = true;
readonly id = uniqueId('exclusive');
@obx.shallow readonly children: Node[] = [];
readonly title: IPublicTypeTitleContent;
@obx.shallow readonly children: INode[] = [];
@obx private visibleIndex = 0;
@ -28,11 +40,11 @@ export class ExclusiveGroup implements IPublicModelExclusiveGroup {
return this.children.length;
}
@computed get visibleNode(): Node {
@computed get visibleNode(): INode {
return this.children[this.visibleIndex];
}
@computed get firstNode(): Node {
@computed get firstNode(): INode {
return this.children[0]!;
}
@ -40,8 +52,16 @@ export class ExclusiveGroup implements IPublicModelExclusiveGroup {
return this.firstNode.index;
}
add(node: Node) {
if (node.nextSibling && node.nextSibling.conditionGroup === this) {
constructor(readonly name: string, title?: IPublicTypeTitleContent) {
makeObservable(this);
this.title = title || {
type: 'i18n',
intl: intl('Condition Group'),
};
}
add(node: INode) {
if (node.nextSibling && node.nextSibling.conditionGroup?.id === this.id) {
const i = this.children.indexOf(node.nextSibling);
this.children.splice(i, 0, node);
} else {
@ -49,7 +69,7 @@ export class ExclusiveGroup implements IPublicModelExclusiveGroup {
}
}
remove(node: Node) {
remove(node: INode) {
const i = this.children.indexOf(node);
if (i > -1) {
this.children.splice(i, 1);
@ -61,27 +81,17 @@ export class ExclusiveGroup implements IPublicModelExclusiveGroup {
}
}
setVisible(node: Node) {
setVisible(node: INode) {
const i = this.children.indexOf(node);
if (i > -1) {
this.visibleIndex = i;
}
}
isVisible(node: Node) {
isVisible(node: INode) {
const i = this.children.indexOf(node);
return i === this.visibleIndex;
}
readonly title: IPublicTypeTitleContent;
constructor(readonly name: string, title?: IPublicTypeTitleContent) {
makeObservable(this);
this.title = title || {
type: 'i18n',
intl: intl('Condition Group'),
};
}
}
export function isExclusiveGroup(obj: any): obj is ExclusiveGroup {

View File

@ -1,15 +1,15 @@
import { Node } from './node';
import { INode } from './node';
import { DocumentModel } from '../document-model';
import { IPublicModelModalNodesManager } from '@alilc/lowcode-types';
import { createModuleEventBus, IEventBus } from '@alilc/lowcode-editor-core';
export function getModalNodes(node: Node) {
export function getModalNodes(node: INode) {
if (!node) return [];
let nodes: any = [];
if (node.componentMeta.isModal) {
nodes.push(node);
}
const children = node.getChildren();
const { children } = node;
if (children) {
children.forEach((child) => {
nodes = nodes.concat(getModalNodes(child));
@ -18,16 +18,15 @@ export function getModalNodes(node: Node) {
return nodes;
}
export interface IModalNodesManager extends IPublicModelModalNodesManager {
export interface IModalNodesManager extends IPublicModelModalNodesManager<INode> {
}
export class ModalNodesManager implements IModalNodesManager {
public willDestroy: any;
willDestroy: any;
private page: DocumentModel;
private modalNodes: Node[];
private modalNodes: INode[];
private nodeRemoveEvents: any;
@ -45,26 +44,27 @@ export class ModalNodesManager implements IModalNodesManager {
];
}
getModalNodes() {
getModalNodes(): INode[] {
return this.modalNodes;
}
getVisibleModalNode() {
return this.getModalNodes().find((node: Node) => node.getVisible());
getVisibleModalNode(): INode | null {
const visibleNode = this.getModalNodes().find((node: INode) => node.getVisible());
return visibleNode || null;
}
hideModalNodes() {
this.modalNodes.forEach((node: Node) => {
this.modalNodes.forEach((node: INode) => {
node.setVisible(false);
});
}
setVisible(node: Node) {
setVisible(node: INode) {
this.hideModalNodes();
node.setVisible(true);
}
setInvisible(node: Node) {
setInvisible(node: INode) {
node.setVisible(false);
}
@ -82,8 +82,8 @@ export class ModalNodesManager implements IModalNodesManager {
};
}
private addNode(node: Node) {
if (node.componentMeta.isModal) {
private addNode(node: INode) {
if (node?.componentMeta.isModal) {
this.hideModalNodes();
this.modalNodes.push(node);
this.addNodeEvent(node);
@ -92,7 +92,7 @@ export class ModalNodesManager implements IModalNodesManager {
}
}
private removeNode(node: Node) {
private removeNode(node: INode) {
if (node.componentMeta.isModal) {
const index = this.modalNodes.indexOf(node);
if (index >= 0) {
@ -106,24 +106,24 @@ export class ModalNodesManager implements IModalNodesManager {
}
}
private addNodeEvent(node: Node) {
this.nodeRemoveEvents[node.getId()] =
private addNodeEvent(node: INode) {
this.nodeRemoveEvents[node.id] =
node.onVisibleChange(() => {
this.emitter.emit('visibleChange');
});
}
private removeNodeEvent(node: Node) {
if (this.nodeRemoveEvents[node.getId()]) {
this.nodeRemoveEvents[node.getId()]();
delete this.nodeRemoveEvents[node.getId()];
private removeNodeEvent(node: INode) {
if (this.nodeRemoveEvents[node.id]) {
this.nodeRemoveEvents[node.id]();
delete this.nodeRemoveEvents[node.id];
}
}
setNodes() {
const nodes = getModalNodes(this.page.getRoot()!);
const nodes = getModalNodes(this.page.rootNode!);
this.modalNodes = nodes;
this.modalNodes.forEach((node: Node) => {
this.modalNodes.forEach((node: INode) => {
this.addNodeEvent(node);
});

View File

@ -1,6 +1,6 @@
import { obx, computed, globalContext, makeObservable, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core';
import { Node, INode } from './node';
import { IPublicTypeNodeData, IPublicModelNodeChildren, IPublicEnumTransformStage } from '@alilc/lowcode-types';
import { IPublicTypeNodeData, IPublicModelNodeChildren, IPublicEnumTransformStage, IPublicTypeDisposable } from '@alilc/lowcode-types';
import { shallowEqual, compatStage, isNodeSchema } from '@alilc/lowcode-utils';
import { foreachReverse } from '../../utils/tree';
import { NodeRemoveOptions } from '../../types';
@ -10,8 +10,18 @@ export interface IOnChangeOptions {
node: Node;
}
export interface INodeChildren extends Omit<IPublicModelNodeChildren, 'forEach' | 'map' | 'every' | 'some' | 'filter' | 'find' | 'reduce' | 'mergeChildren' > {
export interface INodeChildren extends Omit<IPublicModelNodeChildren<INode>,
'importSchema' |
'exportSchema' |
'isEmpty' |
'notEmpty'
> {
get owner(): INode;
get length(): number;
unlinkChild(node: INode): void;
/**
*
*/
@ -39,23 +49,18 @@ export interface INodeChildren extends Omit<IPublicModelNodeChildren, 'forEach'
forEach(fn: (item: INode, index: number) => void): void;
map<T>(fn: (item: INode, index: number) => T): T[] | null;
/**
*
*/
get(index: number): INode | null;
every(fn: (item: INode, index: number) => any): boolean;
isEmpty(): boolean;
some(fn: (item: INode, index: number) => any): boolean;
notEmpty(): boolean;
filter(fn: (item: INode, index: number) => any): any;
internalInitParent(): void;
find(fn: (item: INode, index: number) => boolean): any;
reduce(fn: (acc: any, cur: INode) => any, initialValue: any): void;
mergeChildren(
remover: (node: INode, idx: number) => boolean,
adder: (children: INode[]) => IPublicTypeNodeData[] | null,
sorter: (firstNode: INode, secondNode: INode) => number,
): any;
onChange(fn: (info?: IOnChangeOptions) => void): IPublicTypeDisposable;
/** overriding methods end */
}
@ -64,6 +69,31 @@ export class NodeChildren implements INodeChildren {
private emitter: IEventBus = createModuleEventBus('NodeChildren');
/**
*
*/
@computed get size(): number {
return this.children.length;
}
get isEmptyNode(): boolean {
return this.size < 1;
}
get notEmptyNode(): boolean {
return this.size > 0;
}
@computed get length(): number {
return this.children.length;
}
private purged = false;
get [Symbol.toStringTag]() {
// 保证向前兼容性
return 'Array';
}
constructor(
readonly owner: INode,
data: IPublicTypeNodeData | IPublicTypeNodeData[],
@ -105,12 +135,12 @@ export class NodeChildren implements INodeChildren {
const child = originChildren[i];
const item = data[i];
let node: Node | undefined;
let node: INode | undefined | null;
if (isNodeSchema(item) && !checkId && child && child.componentName === item.componentName) {
node = child;
node.import(item);
} else {
node = this.owner.document.createNode(item, checkId);
node = this.owner.document?.createNode(item, checkId);
}
children[i] = node;
}
@ -130,13 +160,6 @@ export class NodeChildren implements INodeChildren {
return this.children.concat(nodes);
}
/**
*
*/
@computed get size(): number {
return this.children.length;
}
/**
*
*/
@ -144,24 +167,10 @@ export class NodeChildren implements INodeChildren {
return this.isEmptyNode;
}
get isEmptyNode(): boolean {
return this.size < 1;
}
notEmpty() {
return this.notEmptyNode;
}
get notEmptyNode(): boolean {
return this.size > 0;
}
@computed get length(): number {
return this.children.length;
}
private purged = false;
/**
*
*/
@ -422,7 +431,7 @@ export class NodeChildren implements INodeChildren {
return this.children.filter(fn);
}
find(fn: (item: INode, index: number) => boolean) {
find(fn: (item: INode, index: number) => boolean): INode | undefined {
return this.children.find(fn);
}
@ -430,6 +439,10 @@ export class NodeChildren implements INodeChildren {
return this.children.reduce(fn, initialValue);
}
reverse() {
return this.children.reverse();
}
mergeChildren(
remover: (node: INode, idx: number) => boolean,
adder: (children: INode[]) => IPublicTypeNodeData[] | null,
@ -453,7 +466,7 @@ export class NodeChildren implements INodeChildren {
const items = adder(this.children);
if (items && items.length > 0) {
items.forEach((child: IPublicTypeNodeData) => {
const node = this.owner.document?.createNode(child);
const node: INode = this.owner.document?.createNode(child);
this.children.push(node);
node.internalSetParent(this.owner);
});
@ -469,7 +482,7 @@ export class NodeChildren implements INodeChildren {
}
}
onChange(fn: (info?: IOnChangeOptions) => void): () => void {
onChange(fn: (info?: IOnChangeOptions) => void): IPublicTypeDisposable {
this.emitter.on('change', fn);
return () => {
this.emitter.removeListener('change', fn);
@ -483,11 +496,6 @@ export class NodeChildren implements INodeChildren {
};
}
get [Symbol.toStringTag]() {
// 保证向前兼容性
return 'Array';
}
private reportModified(node: INode, owner: INode, options = {}) {
if (!node) {
return;
@ -495,7 +503,7 @@ export class NodeChildren implements INodeChildren {
if (node.isRootNode) {
return;
}
const callbacks = owner.componentMeta?.getMetadata().configure.advanced?.callbacks;
const callbacks = owner.componentMeta?.advanced.callbacks;
if (callbacks?.onSubtreeModified) {
try {
callbacks?.onSubtreeModified.call(

View File

@ -15,18 +15,21 @@ import {
IPublicModelNode,
IPublicModelExclusiveGroup,
IPublicEnumTransformStage,
IPublicTypeDisposable,
IBaseModelNode,
} from '@alilc/lowcode-types';
import { compatStage, isDOMText, isJSExpression } from '@alilc/lowcode-utils';
import { SettingTopEntry } from '@alilc/lowcode-designer';
import { Props, getConvertedExtraKey } from './props/props';
import { DocumentModel, IDocumentModel } from '../document-model';
import { compatStage, isDOMText, isJSExpression, isNode, isNodeSchema } from '@alilc/lowcode-utils';
import { ISettingTopEntry } from '@alilc/lowcode-designer';
import { Props, getConvertedExtraKey, IProps } from './props/props';
import { IDocumentModel } from '../document-model';
import { NodeChildren, INodeChildren } from './node-children';
import { Prop } from './props/prop';
import { ComponentMeta } from '../../component-meta';
import { ExclusiveGroup, isExclusiveGroup } from './exclusive-group';
import { IProp, Prop } from './props/prop';
import { IComponentMeta } from '../../component-meta';
import { ExclusiveGroup, IExclusiveGroup, isExclusiveGroup } from './exclusive-group';
import { includeSlot, removeSlot } from '../../utils/slot';
import { foreachReverse } from '../../utils/tree';
import { NodeRemoveOptions, EDITOR_EVENT } from '../../types';
import { Prop as ShellProp } from '@alilc/lowcode-shell';
export interface NodeStatus {
locking: boolean;
@ -34,17 +37,51 @@ export interface NodeStatus {
inPlaceEditing: boolean;
}
export interface INode extends IPublicModelNode {
export interface INode extends Omit<IBaseModelNode<
IDocumentModel,
INode,
INodeChildren,
IComponentMeta,
ISettingTopEntry,
IProps,
IProp,
IExclusiveGroup
>,
'isRoot' |
'isPage' |
'isComponent' |
'isModal' |
'isSlot' |
'isParental' |
'isLeaf' |
'settingEntry' |
// 在内部的 node 模型中不存在
'getExtraPropValue' |
'setExtraPropValue' |
'exportSchema' |
'visible' |
'importSchema' |
// 内外实现有差异
'isContainer' |
'isEmpty'
> {
isNode: boolean;
setVisible(flag: boolean): void;
get componentMeta(): IComponentMeta;
getVisible(): boolean;
get settingEntry(): ISettingTopEntry;
get isPurged(): boolean;
get index(): number | undefined;
get isPurging(): boolean;
/**
* 使
* @param useMutator
*/
internalSetParent(parent: INode | null, useMutator: boolean): void;
internalSetParent(parent: INode | null, useMutator?: boolean): void;
setConditionGroup(grp: IPublicModelExclusiveGroup | string | null): void;
@ -52,16 +89,12 @@ export interface INode extends IPublicModelNode {
internalPurgeStart(): void;
unlinkSlot(slotNode: Node): void;
didDropOut(dragment: Node): void;
unlinkSlot(slotNode: INode): void;
/**
* schema
*/
export(stage: IPublicEnumTransformStage, options?: any): IPublicTypeNodeSchema;
get document(): IDocumentModel;
export<T = IPublicTypeNodeSchema>(stage: IPublicEnumTransformStage, options?: any): T;
emitPropChange(val: IPublicTypePropChangeOptions): void;
@ -70,6 +103,50 @@ export interface INode extends IPublicModelNode {
internalSetSlotFor(slotFor: Prop | null | undefined): void;
addSlot(slotNode: INode): void;
onVisibleChange(func: (flag: boolean) => any): () => void;
getSuitablePlace(node: INode, ref: any): any;
onChildrenChange(fn: (param?: { type: string; node: INode }) => void): IPublicTypeDisposable | undefined;
onPropChange(func: (info: IPublicTypePropChangeOptions) => void): IPublicTypeDisposable;
isModal(): boolean;
isRoot(): boolean;
isPage(): boolean;
isComponent(): boolean;
isSlot(): boolean;
isParental(): boolean;
isLeaf(): boolean;
isContainer(): boolean;
isEmpty(): boolean;
remove(
useMutator?: boolean,
purge?: boolean,
options?: NodeRemoveOptions,
): void;
didDropIn(dragment: INode): void;
didDropOut(dragment: INode): void;
purge(): void;
removeSlot(slotNode: INode): boolean;
setVisible(flag: boolean): void;
getVisible(): boolean;
}
/**
@ -148,7 +225,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/**
*
*/
props: Props;
props: IProps;
protected _children?: INodeChildren;
@ -204,7 +281,79 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
isInited = false;
constructor(readonly document: IDocumentModel, nodeSchema: Schema, options: any = {}) {
_settingEntry: ISettingTopEntry;
get settingEntry(): ISettingTopEntry {
if (this._settingEntry) return this._settingEntry;
this._settingEntry = this.document.designer.createSettingEntry([this]);
return this._settingEntry;
}
private autoruns?: Array<() => void>;
private _isRGLContainer = false;
set isRGLContainer(status: boolean) {
this._isRGLContainer = status;
}
get isRGLContainer(): boolean {
return !!this._isRGLContainer;
}
set isRGLContainerNode(status: boolean) {
this._isRGLContainer = status;
}
get isRGLContainerNode(): boolean {
return !!this._isRGLContainer;
}
get isEmptyNode() {
return this.isEmpty();
}
private _slotFor?: IProp | null | undefined = null;
@obx.shallow _slots: INode[] = [];
get slots(): INode[] {
return this._slots;
}
/* istanbul ignore next */
@obx.ref private _conditionGroup: IExclusiveGroup | null = null;
/* istanbul ignore next */
get conditionGroup(): IExclusiveGroup | null {
return this._conditionGroup;
}
private purged = false;
/**
*
*/
get isPurged() {
return this.purged;
}
private purging: boolean = false;
/**
*
*/
get isPurging() {
return this.purging;
}
@obx.shallow status: NodeStatus = {
inPlaceEditing: false,
locking: false,
pseudo: false,
};
constructor(readonly document: IDocumentModel, nodeSchema: Schema) {
makeObservable(this);
const { componentName, id, children, props, ...extras } = nodeSchema;
this.id = document.nextId(id);
@ -232,19 +381,11 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
this.onVisibleChange((visible: boolean) => {
editor?.eventBus.emit(EDITOR_EVENT.NODE_VISIBLE_CHANGE, this, visible);
});
this.onChildrenChange((info?: { type: string; node: Node }) => {
this.onChildrenChange((info?: { type: string; node: INode }) => {
editor?.eventBus.emit(EDITOR_EVENT.NODE_VISIBLE_CHANGE, info);
});
}
_settingEntry: SettingTopEntry;
get settingEntry(): SettingTopEntry {
if (this._settingEntry) return this._settingEntry;
this._settingEntry = this.document.designer.createSettingEntry([this]);
return this._settingEntry;
}
/**
* prop reaction
*/
@ -268,16 +409,14 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
return this.document.designer.transformProps(props, this, IPublicEnumTransformStage.Upgrade);
}
private autoruns?: Array<() => void>;
private setupAutoruns() {
const autoruns = this.componentMeta.getMetadata().configure.advanced?.autoruns;
const { autoruns } = this.componentMeta.advanced;
if (!autoruns || autoruns.length < 1) {
return;
}
this.autoruns = autoruns.map((item) => {
return autorun(() => {
item.autorun(this.props.get(item.name, true) as any);
item.autorun(ShellProp.create(this.props.get(item.name, true))!);
});
});
}
@ -285,7 +424,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
private initialChildren(children: any): IPublicTypeNodeData[] {
// FIXME! this is dirty code
if (children == null) {
const initialChildren = this.componentMeta.getMetadata().configure.advanced?.initialChildren;
const { initialChildren } = this.componentMeta.advanced;
if (initialChildren) {
if (typeof initialChildren === 'function') {
return initialChildren(this as any) || [];
@ -296,24 +435,6 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
return children || [];
}
private _isRGLContainer = false;
set isRGLContainer(status: boolean) {
this._isRGLContainer = status;
}
get isRGLContainer(): boolean {
return !!this._isRGLContainer;
}
set isRGLContainerNode(status: boolean) {
this._isRGLContainer = status;
}
get isRGLContainerNode(): boolean {
return !!this._isRGLContainer;
}
isContainer(): boolean {
return this.isContainerNode;
}
@ -388,8 +509,8 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
this.document.addWillPurge(this);
}
private didDropIn(dragment: Node) {
const callbacks = this.componentMeta.getMetadata().configure.advanced?.callbacks;
didDropIn(dragment: INode) {
const { callbacks } = this.componentMeta.advanced;
if (callbacks?.onNodeAdd) {
const cbThis = this.internalToShellNode();
callbacks?.onNodeAdd.call(cbThis, dragment.internalToShellNode(), cbThis);
@ -399,8 +520,8 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
}
}
private didDropOut(dragment: Node) {
const callbacks = this.componentMeta.getMetadata().configure.advanced?.callbacks;
didDropOut(dragment: INode) {
const { callbacks } = this.componentMeta.advanced;
if (callbacks?.onNodeRemove) {
const cbThis = this.internalToShellNode();
callbacks?.onNodeRemove.call(cbThis, dragment.internalToShellNode(), cbThis);
@ -449,8 +570,6 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
}
}
private _slotFor?: Prop | null = null;
internalSetSlotFor(slotFor: Prop | null | undefined) {
this._slotFor = slotFor;
}
@ -462,7 +581,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/**
*
*/
get slotFor() {
get slotFor(): IProp | null | undefined {
return this._slotFor;
}
@ -482,10 +601,10 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
});
}
if (this.isSlot()) {
this.parent.removeSlot(this, purge);
this.parent.children.internalDelete(this, purge, useMutator, { suppressRemoveEvent: true });
this.parent.removeSlot(this);
this.parent.children?.internalDelete(this, purge, useMutator, { suppressRemoveEvent: true });
} else {
this.parent.children.internalDelete(this, purge, useMutator, { suppressRemoveEvent: true });
this.parent.children?.internalDelete(this, purge, useMutator, { suppressRemoveEvent: true });
}
}
}
@ -525,7 +644,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/**
*
*/
@computed get componentMeta(): ComponentMeta {
@computed get componentMeta(): IComponentMeta {
return this.document.getComponentMeta(this.componentName);
}
@ -536,26 +655,13 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
return this.props.export(IPublicEnumTransformStage.Serilize).props || null;
}
@obx.shallow _slots: INode[] = [];
hasSlots() {
return this._slots.length > 0;
}
get slots() {
return this._slots;
}
/* istanbul ignore next */
@obx.ref private _conditionGroup: IPublicModelExclusiveGroup | null = null;
/* istanbul ignore next */
get conditionGroup(): IPublicModelExclusiveGroup | null {
return this._conditionGroup;
}
/* istanbul ignore next */
setConditionGroup(grp: IPublicModelExclusiveGroup | string | null) {
let _grp: IExclusiveGroup | null = null;
if (!grp) {
this.getExtraProp('conditionGroup', false)?.remove();
if (this._conditionGroup) {
@ -566,20 +672,20 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
}
if (!isExclusiveGroup(grp)) {
if (this.prevSibling?.conditionGroup?.name === grp) {
grp = this.prevSibling.conditionGroup;
_grp = this.prevSibling.conditionGroup;
} else if (this.nextSibling?.conditionGroup?.name === grp) {
grp = this.nextSibling.conditionGroup;
} else {
grp = new ExclusiveGroup(grp);
_grp = this.nextSibling.conditionGroup;
} else if (typeof grp === 'string') {
_grp = new ExclusiveGroup(grp);
}
}
if (this._conditionGroup !== grp) {
this.getExtraProp('conditionGroup', true)?.setValue(grp.name);
if (_grp && this._conditionGroup !== _grp) {
this.getExtraProp('conditionGroup', true)?.setValue(_grp.name);
if (this._conditionGroup) {
this._conditionGroup.remove(this);
}
this._conditionGroup = grp;
grp.add(this);
this._conditionGroup = _grp;
_grp?.add(this);
}
}
@ -632,16 +738,20 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/**
*
*
* @param {Node} node
* @param {INode} node
* @param {object} data
*/
replaceChild(node: Node, data: any): Node {
replaceChild(node: INode, data: any): INode | null {
if (this.children?.has(node)) {
const selected = this.document.selection.has(node.id);
delete data.id;
const newNode = this.document.createNode(data);
if (!isNode(newNode)) {
return null;
}
this.insertBefore(newNode, node, false);
node.remove(false);
@ -670,11 +780,11 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
};
}
getProp(path: string, createIfNone = true): Prop | null {
getProp(path: string, createIfNone = true): IProp | null {
return this.props.query(path, createIfNone) || null;
}
getExtraProp(key: string, createIfNone = true): Prop | null {
getExtraProp(key: string, createIfNone = true): IProp | null {
return this.props.get(getConvertedExtraKey(key), createIfNone) || null;
}
@ -724,39 +834,45 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/**
*
*/
@computed get index(): number {
@computed get index(): number | undefined {
if (!this.parent) {
return -1;
}
return this.parent.children.indexOf(this);
return this.parent.children?.indexOf(this);
}
/**
*
*/
get nextSibling(): Node | null {
get nextSibling(): INode | null | undefined {
if (!this.parent) {
return null;
}
const { index } = this;
if (typeof index !== 'number') {
return null;
}
if (index < 0) {
return null;
}
return this.parent.children.get(index + 1);
return this.parent.children?.get(index + 1);
}
/**
*
*/
get prevSibling(): Node | null {
get prevSibling(): INode | null | undefined {
if (!this.parent) {
return null;
}
const { index } = this;
if (typeof index !== 'number') {
return null;
}
if (index < 1) {
return null;
}
return this.parent.children.get(index - 1);
return this.parent.children?.get(index - 1);
}
/**
@ -775,7 +891,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
if (this.isSlot()) {
foreachReverse(
this.children!,
(subNode: Node) => {
(subNode: INode) => {
subNode.remove(true, true);
},
(iterable, idx) => (iterable as NodeChildren).get(idx),
@ -798,7 +914,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/**
* schema
*/
export(stage: IPublicEnumTransformStage = IPublicEnumTransformStage.Save, options: any = {}): Schema {
export<T = IPublicTypeNodeSchema>(stage: IPublicEnumTransformStage = IPublicEnumTransformStage.Save, options: any = {}): T {
stage = compatStage(stage);
const baseSchema: any = {
componentName: this.componentName,
@ -840,7 +956,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
...this.document.designer.transformProps(_extras_, this, stage),
};
if (this.isParental() && this.children.size > 0 && !options.bypassChildren) {
if (this.isParental() && this.children && this.children.size > 0 && !options.bypassChildren) {
schema.children = this.children.export(stage);
}
@ -850,14 +966,14 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/**
*
*/
contains(node: Node): boolean {
contains(node: INode): boolean {
return contains(this, node);
}
/**
*
*/
getZLevelTop(zLevel: number): Node | null {
getZLevelTop(zLevel: number): INode | null {
return getZLevelTop(this, zLevel);
}
@ -869,11 +985,11 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
* 2 thisNode before or after otherNode
* 0 thisNode same as otherNode
*/
comparePosition(otherNode: Node): PositionNO {
comparePosition(otherNode: INode): PositionNO {
return comparePosition(this, otherNode);
}
unlinkSlot(slotNode: Node) {
unlinkSlot(slotNode: INode) {
const i = this._slots.indexOf(slotNode);
if (i < 0) {
return false;
@ -884,7 +1000,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/**
* Slot节点
*/
removeSlot(slotNode: Node, purge = false): boolean {
removeSlot(slotNode: INode): boolean {
// if (purge) {
// // should set parent null
// slotNode?.internalSetParent(null, false);
@ -925,19 +1041,10 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
*
* @param node
*/
removeChild(node: Node) {
removeChild(node: INode) {
this.children?.delete(node);
}
private purged = false;
/**
*
*/
get isPurged() {
return this.purged;
}
/**
*
*/
@ -952,18 +1059,10 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
// this.document.destroyNode(this);
}
private purging: boolean = false;
internalPurgeStart() {
this.purging = true;
}
/**
*
*/
get isPurging() {
return this.purging;
}
/**
* action
*/
@ -993,18 +1092,18 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
return this.componentName;
}
insert(node: Node, ref?: Node, useMutator = true) {
insert(node: INode, ref?: INode, useMutator = true) {
this.insertAfter(node, ref, useMutator);
}
insertBefore(node: Node, ref?: Node, useMutator = true) {
insertBefore(node: INode, ref?: INode, useMutator = true) {
const nodeInstance = ensureNode(node, this.document);
this.children?.internalInsert(nodeInstance, ref ? ref.index : null, useMutator);
}
insertAfter(node: any, ref?: Node, useMutator = true) {
insertAfter(node: any, ref?: INode, useMutator = true) {
const nodeInstance = ensureNode(node, this.document);
this.children?.internalInsert(nodeInstance, ref ? ref.index + 1 : null, useMutator);
this.children?.internalInsert(nodeInstance, ref ? (ref.index || 0) + 1 : null, useMutator);
}
getParent() {
@ -1031,25 +1130,19 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
return this.props;
}
onChildrenChange(fn: (param?: { type: string; node: Node }) => void): (() => void) | undefined {
onChildrenChange(fn: (param?: { type: string; node: INode }) => void): IPublicTypeDisposable | undefined {
const wrappedFunc = wrapWithEventSwitch(fn);
return this.children?.onChange(wrappedFunc);
}
mergeChildren(
remover: () => any,
adder: (children: Node[]) => IPublicTypeNodeData[] | null,
sorter: () => any,
remover: (node: INode, idx: number) => any,
adder: (children: INode[]) => IPublicTypeNodeData[] | null,
sorter: (firstNode: INode, secondNode: INode) => any,
) {
this.children?.mergeChildren(remover, adder, sorter);
}
@obx.shallow status: NodeStatus = {
inPlaceEditing: false,
locking: false,
pseudo: false,
};
/**
* @deprecated
*/
@ -1107,16 +1200,16 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
}
/**
* TODO: replace non standard metas with standard ones.
* @deprecated no one is using this, will be removed in a future release
*/
getSuitablePlace(node: Node, ref: any): any {
getSuitablePlace(node: INode, ref: any): any {
const focusNode = this.document?.focusNode;
// 如果节点是模态框,插入到根节点下
if (node?.componentMeta?.isModal) {
return { container: focusNode, ref };
}
if (!ref && this.contains(focusNode)) {
if (!ref && focusNode && this.contains(focusNode)) {
const rootCanDropIn = focusNode.componentMeta?.prototype?.options?.canDropIn;
if (
rootCanDropIn === undefined ||
@ -1131,7 +1224,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
if (this.isRoot() && this.children) {
const dropElement = this.children.filter((c) => {
if (!c.isContainer()) {
if (!c.isContainerNode) {
return false;
}
const canDropIn = c.componentMeta?.prototype?.options?.canDropIn;
@ -1230,7 +1323,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
this.emitter?.emit('propChange', val);
}
onPropChange(func: (info: IPublicTypePropChangeOptions) => void): Function {
onPropChange(func: (info: IPublicTypePropChangeOptions) => void): IPublicTypeDisposable {
const wrappedFunc = wrapWithEventSwitch(func);
this.emitter.on('propChange', wrappedFunc);
return () => {
@ -1239,7 +1332,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
}
}
function ensureNode(node: any, document: DocumentModel): Node {
function ensureNode(node: any, document: IDocumentModel): INode {
let nodeInstance = node;
if (!isNode(node)) {
if (node.getComponentName) {
@ -1264,22 +1357,15 @@ export type PageNode = Node<IPublicTypePageSchema>;
export type ComponentNode = Node<IPublicTypeComponentSchema>;
export type RootNode = PageNode | ComponentNode;
/**
* @deprecated use same function from '@alilc/lowcode-utils' instead
*/
export function isNode(node: any): node is Node {
return node && node.isNode;
export function isRootNode(node: INode): node is INode {
return node && node.isRootNode;
}
export function isRootNode(node: Node): node is RootNode {
return node && node.isRoot();
}
export function isLowCodeComponent(node: Node): boolean {
export function isLowCodeComponent(node: INode): node is INode {
return node.componentMeta?.getMetadata().devMode === 'lowCode';
}
export function getZLevelTop(child: Node, zLevel: number): Node | null {
export function getZLevelTop(child: INode, zLevel: number): INode | null {
let l = child.zLevel;
if (l < zLevel || zLevel < 0) {
return null;
@ -1300,12 +1386,12 @@ export function getZLevelTop(child: Node, zLevel: number): Node | null {
* @param node2
* @returns
*/
export function contains(node1: Node, node2: Node): boolean {
export function contains(node1: INode, node2: INode): boolean {
if (node1 === node2) {
return true;
}
if (!node1.isParental() || !node2.parent) {
if (!node1.isParentalNode || !node2.parent) {
return false;
}
@ -1327,7 +1413,7 @@ export enum PositionNO {
BeforeOrAfter = 2,
TheSame = 0,
}
export function comparePosition(node1: Node, node2: Node): PositionNO {
export function comparePosition(node1: INode, node2: INode): PositionNO {
if (node1 === node2) {
return PositionNO.TheSame;
}
@ -1356,34 +1442,38 @@ export function comparePosition(node1: Node, node2: Node): PositionNO {
export function insertChild(
container: INode,
thing: Node | IPublicTypeNodeData,
thing: INode | IPublicTypeNodeData,
at?: number | null,
copy?: boolean,
): Node {
let node: Node;
if (isNode(thing) && (copy || thing.isSlot())) {
thing = thing.export(IPublicEnumTransformStage.Clone);
}
if (isNode(thing)) {
): INode | null {
let node: INode | null | RootNode | undefined;
let nodeSchema: IPublicTypeNodeSchema;
if (isNode<INode>(thing) && (copy || thing.isSlot())) {
nodeSchema = thing.export(IPublicEnumTransformStage.Clone);
node = container.document?.createNode(nodeSchema);
} else if (isNode<INode>(thing)) {
node = thing;
} else {
node = container.document.createNode(thing);
} else if (isNodeSchema(thing)) {
node = container.document?.createNode(thing);
}
container.children.internalInsert(node, at);
if (isNode<INode>(node)) {
container.children?.insert(node, at);
return node;
}
return node;
return null;
}
export function insertChildren(
container: INode,
nodes: Node[] | IPublicTypeNodeData[],
nodes: INode[] | IPublicTypeNodeData[],
at?: number | null,
copy?: boolean,
): Node[] {
): INode[] {
let index = at;
let node: any;
const results: Node[] = [];
const results: INode[] = [];
// eslint-disable-next-line no-cond-assign
while ((node = nodes.pop())) {
node = insertChild(container, node, index, copy);

View File

@ -2,7 +2,7 @@ import { untracked, computed, obx, engineConfig, action, makeObservable, mobx, r
import { IPublicTypeCompositeValue, GlobalEvent, IPublicTypeJSSlot, IPublicTypeSlotSchema, IPublicEnumTransformStage, IPublicModelProp } from '@alilc/lowcode-types';
import { uniqueId, isPlainObject, hasOwnProperty, compatStage, isJSExpression, isJSSlot } from '@alilc/lowcode-utils';
import { valueToSource } from './value-to-source';
import { Props, IProps, IPropParent } from './props';
import { IProps, IPropParent } from './props';
import { SlotNode, INode } from '../node';
// import { TransformStage } from '../transform-stage';
@ -11,9 +11,11 @@ export const UNSET = Symbol.for('unset');
// eslint-disable-next-line no-redeclare
export type UNSET = typeof UNSET;
export interface IProp extends Omit<IPublicModelProp, 'exportSchema' | 'node'> {
export interface IProp extends Omit<IPublicModelProp<
INode
>, 'exportSchema' | 'node' > {
readonly props: Props;
readonly props: IProps;
readonly owner: INode;
@ -22,6 +24,12 @@ export interface IProp extends Omit<IPublicModelProp, 'exportSchema' | 'node'> {
export(stage: IPublicEnumTransformStage): IPublicTypeCompositeValue;
getNode(): INode;
getAsString(): string;
unset(): void;
get value(): IPublicTypeCompositeValue | UNSET;
}
export type ValueTypes = 'unset' | 'literal' | 'map' | 'list' | 'expression' | 'slot';
@ -41,7 +49,7 @@ export class Prop implements IProp, IPropParent {
*/
@obx spread: boolean;
readonly props: Props;
readonly props: IProps;
readonly options: any;
@ -113,8 +121,8 @@ export class Prop implements IProp, IPropParent {
private _slotNode?: INode;
get slotNode(): INode | undefined | null {
return this._slotNode;
get slotNode(): INode | null {
return this._slotNode || null;
}
@obx.shallow private _items: Prop[] | null = null;
@ -301,7 +309,7 @@ export class Prop implements IProp, IPropParent {
return this._value;
}
const values = this.items!.map((prop) => {
return prop.export(stage);
return prop?.export(stage);
});
if (values.every((val) => val === undefined)) {
return undefined;
@ -399,10 +407,10 @@ export class Prop implements IProp, IPropParent {
slotSchema = {
componentName: 'Slot',
title: value.title || value.props?.slotTitle,
id: data.id,
id: value.id,
name: value.name || value.props?.slotName,
params: value.params || value.props?.slotParams,
children: data.value,
children: value.children,
} as IPublicTypeSlotSchema;
} else {
slotSchema = {

View File

@ -1,8 +1,8 @@
import { computed, makeObservable, obx, action } from '@alilc/lowcode-editor-core';
import { IPublicTypePropsMap, IPublicTypePropsList, IPublicTypeCompositeValue, IPublicEnumTransformStage, IPublicModelProps } from '@alilc/lowcode-types';
import { IPublicTypePropsMap, IPublicTypePropsList, IPublicTypeCompositeValue, IPublicEnumTransformStage, IBaseModelProps } from '@alilc/lowcode-types';
import { uniqueId, compatStage } from '@alilc/lowcode-utils';
import { Prop, IProp, UNSET } from './prop';
import { INode, Node } from '../node';
import { INode } from '../node';
// import { TransformStage } from '../transform-stage';
interface ExtrasObject {
@ -33,17 +33,29 @@ export interface IPropParent {
get path(): string[];
delete(prop: Prop): void;
}
export interface IProps extends Omit<IPublicModelProps, 'getProp' | 'getExtraProp' | 'getExtraPropValue' | 'setExtraPropValue' | 'node'> {
export interface IProps extends Omit<IBaseModelProps<IProp>, | 'getExtraProp' | 'getExtraPropValue' | 'setExtraPropValue' | 'node'> {
/**
* props node
*/
getNode(): INode;
getProp(path: string): IProp | null;
get(path: string, createIfNone?: boolean): Prop | null;
export(stage?: IPublicEnumTransformStage): {
props?: IPublicTypePropsMap | IPublicTypePropsList;
extras?: ExtrasObject;
};
merge(value: IPublicTypePropsMap, extras?: IPublicTypePropsMap): void;
purge(): void;
query(path: string, createIfNone: boolean): Prop | null;
import(value?: IPublicTypePropsMap | IPublicTypePropsList | null, extras?: ExtrasObject): void;
}
export class Props implements IProps, IPropParent {

View File

@ -1,9 +1,9 @@
import { obx, makeObservable, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core';
import { Node, comparePosition, PositionNO } from './node/node';
import { INode, comparePosition, PositionNO } from './node/node';
import { DocumentModel } from './document-model';
import { IPublicModelSelection } from '@alilc/lowcode-types';
export interface ISelection extends IPublicModelSelection {
export interface ISelection extends Omit<IPublicModelSelection<INode>, 'node'> {
}
@ -105,7 +105,7 @@ export class Selection implements ISelection {
/**
*
*/
containsNode(node: Node, excludeRoot = false) {
containsNode(node: INode, excludeRoot = false) {
for (const id of this._selected) {
const parent = this.doc.getNode(id);
if (excludeRoot && parent?.contains(this.doc.focusNode)) {
@ -121,8 +121,8 @@ export class Selection implements ISelection {
/**
*
*/
getNodes(): Node[] {
const nodes = [];
getNodes(): INode[] {
const nodes: INode[] = [];
for (const id of this._selected) {
const node = this.doc.getNode(id);
if (node) {

View File

@ -6,5 +6,6 @@
"unlock": "Unlock",
"Condition Group": "Condition Group",
"No opened document": "No opened document, open some document to editing",
"locked": "locked"
"locked": "locked",
"Item": "Item"
}

View File

@ -6,5 +6,6 @@
"unlock": "解锁",
"Condition Group": "条件组",
"No opened document": "没有打开的页面,请选择页面打开编辑",
"locked": "已锁定"
"locked": "已锁定",
"Item": "项目"
}

View File

@ -16,6 +16,7 @@ import {
IPublicApiPlugins,
IPublicTypePluginDeclaration,
IPublicApiCanvas,
IPublicApiWorkspace,
} from '@alilc/lowcode-types';
import {
IPluginContextOptions,
@ -24,8 +25,8 @@ import {
} from './plugin-types';
import { isValidPreferenceKey } from './plugin-utils';
export default class PluginContext implements IPublicModelPluginContext, ILowCodePluginContextPrivate {
export default class PluginContext implements
IPublicModelPluginContext, ILowCodePluginContextPrivate {
hotkey: IPublicApiHotkey;
project: IPublicApiProject;
skeleton: IPublicApiSkeleton;
@ -39,6 +40,7 @@ export default class PluginContext implements IPublicModelPluginContext, ILowCod
preference: IPluginPreferenceMananger;
pluginEvent: IPublicApiEvent;
canvas: IPublicApiCanvas;
workspace: IPublicApiWorkspace;
constructor(
options: IPluginContextOptions,

View File

@ -26,12 +26,12 @@ export interface ILowCodePluginRuntimeCore {
disabled: boolean;
config: IPublicTypePluginConfig;
logger: IPublicApiLogger;
meta: IPublicTypePluginMeta;
init(forceInit?: boolean): void;
isInited(): boolean;
destroy(): void;
toProxy(): any;
setDisabled(flag: boolean): void;
meta: IPublicTypePluginMeta;
}
interface ILowCodePluginRuntimeExportsAccessor {

View File

@ -1,6 +1,6 @@
import { obx, computed, makeObservable, action, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core';
import { Designer } from '../designer';
import { DocumentModel, isDocumentModel } from '../document';
import { IDesigner } from '../designer';
import { DocumentModel, IDocumentModel, isDocumentModel } from '../document';
import {
IPublicTypeProjectSchema,
IPublicTypeRootSchema,
@ -12,14 +12,80 @@ import {
import { isLowCodeComponentType, isProCodeComponentType } from '@alilc/lowcode-utils';
import { ISimulatorHost } from '../simulator';
export interface IProject extends IPublicApiProject {
export interface IProject extends Omit< IPublicApiProject,
'simulatorHost' |
'importSchema' |
'exportSchema' |
'openDocument' |
'getDocumentById' |
'getCurrentDocument' |
'addPropsTransducer' |
'onRemoveDocument' |
'onChangeDocument' |
'onSimulatorHostReady' |
'onSimulatorRendererReady' |
'setI18n' |
'currentDocument' |
'selection' |
'documents' |
'createDocument' |
'getDocumentByFileName'
> {
get designer(): IDesigner;
get simulator(): ISimulatorHost | null;
get currentDocument(): IDocumentModel | null | undefined;
get documents(): IDocumentModel[];
open(doc?: string | IDocumentModel | IPublicTypeRootSchema): IDocumentModel | null;
getDocumentByFileName(fileName: string): IDocumentModel | null;
createDocument(data?: IPublicTypeRootSchema): IDocumentModel;
load(schema?: IPublicTypeProjectSchema, autoOpen?: boolean | string): void;
getSchema(
stage: IPublicEnumTransformStage,
): IPublicTypeProjectSchema;
getDocument(id: string): IDocumentModel | null;
onCurrentDocumentChange(fn: (doc: IDocumentModel) => void): () => void;
onSimulatorReady(fn: (args: any) => void): () => void;
onRendererReady(fn: () => void): () => void;
/**
*
*/
set(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
key:
| 'version'
| 'componentsTree'
| 'componentsMap'
| 'utils'
| 'constants'
| 'i18n'
| 'css'
| 'dataSource'
| string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
value: any,
): void;
checkExclusive(activeDoc: DocumentModel): void;
}
export class Project implements IProject {
private emitter: IEventBus = createModuleEventBus('Project');
@obx.shallow readonly documents: IPublicModelDocumentModel[] = [];
@obx.shallow readonly documents: IDocumentModel[] = [];
private data: IPublicTypeProjectSchema = {
version: '1.0.0',
@ -39,16 +105,7 @@ export class Project implements IProject {
return this._simulator || null;
}
key = Math.random();
// TODO: 考虑项目级别 History
constructor(readonly designer: Designer, schema?: IPublicTypeProjectSchema, readonly viewName = 'global') {
makeObservable(this);
this.load(schema);
}
@computed get currentDocument() {
@computed get currentDocument(): IDocumentModel | null | undefined {
return this.documents.find((doc) => doc.active);
}
@ -69,15 +126,22 @@ export class Project implements IProject {
this._i18n = value || {};
}
private documentsMap = new Map<string, DocumentModel>();
constructor(readonly designer: IDesigner, schema?: IPublicTypeProjectSchema, readonly viewName = 'global') {
makeObservable(this);
this.load(schema);
}
private getComponentsMap(): IPublicTypeComponentsMap {
return this.documents.reduce((
compomentsMap: IPublicTypeComponentsMap,
componentsMap: IPublicTypeComponentsMap,
curDoc: DocumentModel,
) => {
const curComponentsMap = curDoc.getComponentsMap();
if (Array.isArray(curComponentsMap)) {
curComponentsMap.forEach((item) => {
const found = compomentsMap.find((eItem) => {
const found = componentsMap.find((eItem) => {
if (
isProCodeComponentType(eItem) &&
isProCodeComponentType(item) &&
@ -94,17 +158,19 @@ export class Project implements IProject {
return false;
});
if (found) return;
compomentsMap.push(item);
componentsMap.push(item);
});
}
return compomentsMap;
return componentsMap;
}, [] as IPublicTypeComponentsMap);
}
/**
* schema
*/
getSchema(stage: IPublicEnumTransformStage = IPublicEnumTransformStage.Save): IPublicTypeProjectSchema {
getSchema(
stage: IPublicEnumTransformStage = IPublicEnumTransformStage.Save,
): IPublicTypeProjectSchema {
return {
...this.data,
componentsMap: this.getComponentsMap(),
@ -240,26 +306,24 @@ export class Project implements IProject {
return Reflect.get(this.data, key);
}
private documentsMap = new Map<string, DocumentModel>();
getDocument(id: string): DocumentModel | null {
getDocument(id: string): IDocumentModel | null {
// 此处不能使用 this.documentsMap.get(id),因为在乐高 rollback 场景document.id 会被改成其他值
return this.documents.find((doc) => doc.id === id) || null;
}
getDocumentByFileName(fileName: string): DocumentModel | null {
getDocumentByFileName(fileName: string): IDocumentModel | null {
return this.documents.find((doc) => doc.fileName === fileName) || null;
}
@action
createDocument(data?: IPublicTypeRootSchema): DocumentModel {
createDocument(data?: IPublicTypeRootSchema): IDocumentModel {
const doc = new DocumentModel(this, data || this?.data?.componentsTree?.[0]);
this.documents.push(doc);
this.documentsMap.set(doc.id, doc);
return doc;
}
open(doc?: string | DocumentModel | IPublicTypeRootSchema): DocumentModel | null {
open(doc?: string | IDocumentModel | IPublicTypeRootSchema): IDocumentModel | null {
if (!doc) {
const got = this.documents.find((item) => item.isBlank());
if (got) {
@ -325,6 +389,10 @@ export class Project implements IProject {
}
onSimulatorReady(fn: (args: any) => void): () => void {
if (this._simulator) {
fn(this._simulator);
return () => {};
}
this.emitter.on('lowcode_engine_simulator_ready', fn);
return () => {
this.emitter.removeListener('lowcode_engine_simulator_ready', fn);
@ -341,7 +409,7 @@ export class Project implements IProject {
};
}
onCurrentDocumentChange(fn: (doc: DocumentModel) => void): () => void {
onCurrentDocumentChange(fn: (doc: IDocumentModel) => void): () => void {
this.emitter.on('current-document.change', fn);
return () => {
this.emitter.removeListener('current-document.change', fn);

View File

@ -1,14 +1,14 @@
import { ComponentType } from 'react';
import { IPublicTypeComponentMetadata, IPublicTypeNodeSchema, IPublicModelScrollable, IPublicTypeComponentInstance, IPublicModelSensor, IPublicTypeNodeInstance } from '@alilc/lowcode-types';
import { IPublicTypeComponentMetadata, IPublicTypeNodeSchema, IPublicTypeScrollable, IPublicTypeComponentInstance, IPublicModelSensor, IPublicTypeNodeInstance } from '@alilc/lowcode-types';
import { Point, ScrollTarget, ILocateEvent } from './designer';
import { BuiltinSimulatorRenderer } from './builtin-simulator/renderer';
import { Node, INode } from './document';
import { INode } from './document';
export type AutoFit = '100%';
// eslint-disable-next-line no-redeclare
export const AutoFit = '100%';
export interface IScrollable extends IPublicModelScrollable {
export interface IScrollable extends IPublicTypeScrollable {
}
export interface IViewport extends IScrollable {
@ -132,7 +132,7 @@ export interface ISimulatorHost<P = object> extends IPublicModelSensor {
/**
*
*/
scrollToNode(node: Node, detail?: any): void;
scrollToNode(node: INode, detail?: any): void;
/**
*
@ -147,7 +147,7 @@ export interface ISimulatorHost<P = object> extends IPublicModelSensor {
/**
*
*/
getComponentInstances(node: Node): IPublicTypeComponentInstance[] | null;
getComponentInstances(node: INode): IPublicTypeComponentInstance[] | null;
/**
* schema
@ -157,11 +157,11 @@ export interface ISimulatorHost<P = object> extends IPublicModelSensor {
/**
*
*/
getComponentContext(node: Node): object | null;
getComponentContext(node: INode): object | null;
getClosestNodeInstance(from: IPublicTypeComponentInstance, specId?: string): IPublicTypeNodeInstance | null;
computeRect(node: Node): DOMRect | null;
computeRect(node: INode): DOMRect | null;
computeComponentInstanceRect(instance: IPublicTypeComponentInstance, selector?: string): DOMRect | null;
@ -189,6 +189,6 @@ export function isSimulatorHost(obj: any): obj is ISimulatorHost {
export type Component = ComponentType<any> | object;
export interface INodeSelector {
node: Node;
node: INode;
instance?: IPublicTypeComponentInstance;
}

View File

@ -7,11 +7,11 @@ import {
import { Designer } from '../../src/designer/designer';
import formSchema from '../fixtures/schema/form';
import { fireEvent } from '@testing-library/react';
import { isInLiveEditing, builtinHotkey } from '../../../engine/src/inner-plugins/builtin-hotkey';
import { builtinHotkey } from '../../../engine/src/inner-plugins/builtin-hotkey';
import { shellModelFactory } from '../../../engine/src/modules/shell-model-factory';
import { ILowCodePluginContextPrivate, LowCodePluginManager } from '@alilc/lowcode-designer';
import { IPublicApiPlugins } from '@alilc/lowcode-types';
import { Logger, Project } from '@alilc/lowcode-shell';
import { Logger, Project, Canvas } from '@alilc/lowcode-shell';
import { Workspace } from '@alilc/lowcode-workspace';
const editor = new Editor();
@ -19,12 +19,6 @@ const workspace = new Workspace();
let designer: Designer;
describe('error scenarios', () => {
it('edtior not registered', () => {
expect(isInLiveEditing()).toBeUndefined();
});
});
// keyCode 对应表https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
// hotkey 模块底层用的 keyCode所以还不能用 key / code 测试
describe('快捷键测试', () => {
@ -40,6 +34,7 @@ describe('快捷键测试', () => {
context.hotkey = hotkey;
context.logger = logger;
context.project = project;
context.canvas = new Canvas(editor);
}
};
pluginManager = new LowCodePluginManager(contextApiAssembler).toProxy();

View File

@ -23,7 +23,7 @@ describe('document-model 测试', () => {
it('empty schema', () => {
const doc = new DocumentModel(project);
expect(doc.rootNode.id).toBe('root');
expect(doc.rootNode?.id).toBe('root');
expect(doc.currentRoot).toBe(doc.rootNode);
expect(doc.root).toBe(doc.rootNode);
expect(doc.modalNode).toBeUndefined();

View File

@ -1,8 +1,8 @@
import set from 'lodash/set';
import cloneDeep from 'lodash/cloneDeep';
import '../../fixtures/window';
import { Project } from '../../../src/project/project';
import { Node } from '../../../src/document/node/node';
import { Project, IProject } from '../../../src/project/project';
import { Node, INode } from '../../../src/document/node/node';
import { Designer } from '../../../src/designer/designer';
import formSchema from '../../fixtures/schema/form';
import { getIdsFromSchema, getNodeFromSchemaById } from '../../utils';
@ -17,6 +17,9 @@ jest.mock('../../../src/designer/designer', () => {
getMetadata() {
return { configure: { advanced: null } };
},
get advanced() {
return {};
},
};
},
transformProps(props) { return props; },
@ -34,7 +37,7 @@ beforeAll(() => {
describe('schema 生成节点模型测试', () => {
describe('block ❌ | component ❌ | slot ❌', () => {
let project: Project;
let project: IProject;
beforeEach(() => {
project = new Project(designer, {
componentsTree: [
@ -49,12 +52,12 @@ describe('schema 生成节点模型测试', () => {
it('基本的节点模型初始化,模型导出', () => {
expect(project).toBeTruthy();
const { currentDocument } = project;
const { nodesMap } = currentDocument;
const nodesMap = currentDocument?.nodesMap;
const ids = getIdsFromSchema(formSchema);
const expectedNodeCnt = ids.length;
expect(nodesMap.size).toBe(expectedNodeCnt);
expect(nodesMap?.size).toBe(expectedNodeCnt);
ids.forEach(id => {
expect(nodesMap.get(id).componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName);
expect(nodesMap?.get(id)?.componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName);
});
const pageNode = currentDocument?.getNode('page');
@ -73,18 +76,18 @@ describe('schema 生成节点模型测试', () => {
it('基本的节点模型初始化,节点深度', () => {
expect(project).toBeTruthy();
const { currentDocument } = project;
const getNode = currentDocument.getNode.bind(currentDocument);
const getNode = currentDocument?.getNode.bind(currentDocument);
const pageNode = getNode('page');
const rootHeaderNode = getNode('node_k1ow3cba');
const rootContentNode = getNode('node_k1ow3cbb');
const rootFooterNode = getNode('node_k1ow3cbc');
const formNode = getNode('form');
const cardNode = getNode('node_k1ow3cbj');
const cardContentNode = getNode('node_k1ow3cbk');
const columnsLayoutNode = getNode('node_k1ow3cbw');
const columnNode = getNode('node_k1ow3cbx');
const textFieldNode = getNode('node_k1ow3cbz');
const pageNode = getNode?.('page');
const rootHeaderNode = getNode?.('node_k1ow3cba');
const rootContentNode = getNode?.('node_k1ow3cbb');
const rootFooterNode = getNode?.('node_k1ow3cbc');
const formNode = getNode?.('form');
const cardNode = getNode?.('node_k1ow3cbj');
const cardContentNode = getNode?.('node_k1ow3cbk');
const columnsLayoutNode = getNode?.('node_k1ow3cbw');
const columnNode = getNode?.('node_k1ow3cbx');
const textFieldNode = getNode?.('node_k1ow3cbz');
expect(pageNode?.zLevel).toBe(0);
expect(rootHeaderNode?.zLevel).toBe(1);
@ -128,7 +131,7 @@ describe('schema 生成节点模型测试', () => {
const textFieldNode = getNode('node_k1ow3cbz');
expect(pageNode?.index).toBe(-1);
expect(pageNode?.children.toString()).toBe('[object Array]');
expect(pageNode?.children?.toString()).toBe('[object Array]');
expect(pageNode?.children?.get(1)).toBe(rootContentNode);
expect(pageNode?.getChildren()?.get(1)).toBe(rootContentNode);
expect(pageNode?.getNode()).toBe(pageNode);
@ -159,20 +162,20 @@ describe('schema 生成节点模型测试', () => {
it('基本的节点模型初始化,节点新建、删除等事件', () => {
expect(project).toBeTruthy();
const { currentDocument } = project;
const getNode = currentDocument.getNode.bind(currentDocument);
const createNode = currentDocument.createNode.bind(currentDocument);
const getNode = currentDocument?.getNode.bind(currentDocument);
const createNode = currentDocument?.createNode.bind(currentDocument);
const pageNode = getNode('page');
const pageNode = getNode?.('page');
const nodeCreateHandler = jest.fn();
const offCreate = currentDocument?.onNodeCreate(nodeCreateHandler);
const node = createNode({
const node = createNode?.({
componentName: 'TextInput',
props: {
propA: 'haha',
},
});
currentDocument?.insertNode(pageNode, node);
pageNode && node && currentDocument?.insertNode(pageNode, node);
expect(nodeCreateHandler).toHaveBeenCalledTimes(1);
expect(nodeCreateHandler.mock.calls[0][0]).toBe(node);
@ -181,7 +184,7 @@ describe('schema 生成节点模型测试', () => {
const nodeDestroyHandler = jest.fn();
const offDestroy = currentDocument?.onNodeDestroy(nodeDestroyHandler);
node.remove();
node?.remove();
expect(nodeDestroyHandler).toHaveBeenCalledTimes(1);
expect(nodeDestroyHandler.mock.calls[0][0]).toBe(node);
expect(nodeDestroyHandler.mock.calls[0][0].componentName).toBe('TextInput');
@ -287,9 +290,9 @@ describe('schema 生成节点模型测试', () => {
expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project;
const { nodesMap } = currentDocument;
const formNode = nodesMap.get('form');
currentDocument?.insertNode(formNode, {
const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap?.get('form');
formNode && currentDocument?.insertNode(formNode, {
componentName: 'TextInput',
id: 'nodeschema-id1',
props: {
@ -297,11 +300,11 @@ describe('schema 生成节点模型测试', () => {
propB: 3,
},
}, 0);
expect(nodesMap.size).toBe(ids.length + 1);
expect(formNode.children.length).toBe(4);
const insertedNode = formNode.children.get(0);
expect(insertedNode.componentName).toBe('TextInput');
expect(insertedNode.propsData).toEqual({
expect(nodesMap?.size).toBe(ids.length + 1);
expect(formNode?.children?.length).toBe(4);
const insertedNode = formNode?.children?.get(0);
expect(insertedNode?.componentName).toBe('TextInput');
expect(insertedNode?.propsData).toEqual({
propA: 'haha',
propB: 3,
});
@ -313,9 +316,9 @@ describe('schema 生成节点模型测试', () => {
expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project;
const { nodesMap } = currentDocument;
const formNode = nodesMap.get('form');
currentDocument?.insertNode(formNode, {
const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap?.get('form');
formNode && currentDocument?.insertNode(formNode, {
componentName: 'TextInput',
id: 'nodeschema-id1',
props: {
@ -323,11 +326,11 @@ describe('schema 生成节点模型测试', () => {
propB: 3,
},
}, 1);
expect(nodesMap.size).toBe(ids.length + 1);
expect(formNode.children.length).toBe(4);
const insertedNode = formNode.children.get(1);
expect(insertedNode.componentName).toBe('TextInput');
expect(insertedNode.propsData).toEqual({
expect(nodesMap?.size).toBe(ids.length + 1);
expect(formNode?.children?.length).toBe(4);
const insertedNode = formNode?.children?.get(1);
expect(insertedNode?.componentName).toBe('TextInput');
expect(insertedNode?.propsData).toEqual({
propA: 'haha',
propB: 3,
});
@ -339,8 +342,8 @@ describe('schema 生成节点模型测试', () => {
expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project;
const { nodesMap } = currentDocument;
const formNode = nodesMap.get('form') as Node;
const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap?.get('form') as INode;
currentDocument?.insertNode(formNode, {
componentName: 'ParentNode',
props: {
@ -364,8 +367,8 @@ describe('schema 生成节点模型测试', () => {
},
],
});
expect(nodesMap.size).toBe(ids.length + 3);
expect(formNode.children.length).toBe(4);
expect(nodesMap?.size).toBe(ids.length + 3);
expect(formNode.children?.length).toBe(4);
expect(formNode.children?.get(3)?.componentName).toBe('ParentNode');
expect(formNode.children?.get(3)?.children?.get(0)?.componentName).toBe('SubNode');
expect(formNode.children?.get(3)?.children?.get(1)?.componentName).toBe('SubNode2');
@ -375,9 +378,9 @@ describe('schema 生成节点模型测试', () => {
expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project;
const { nodesMap } = currentDocument;
const formNode = nodesMap.get('form');
currentDocument?.insertNode(formNode, {
const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap?.get('form');
formNode && currentDocument?.insertNode(formNode, {
componentName: 'TextInput',
id: 'nodeschema-id1',
props: {
@ -385,17 +388,17 @@ describe('schema 生成节点模型测试', () => {
propB: 3,
},
});
expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput');
expect(nodesMap.size).toBe(ids.length + 1);
expect(nodesMap?.get('nodeschema-id1')?.componentName).toBe('TextInput');
expect(nodesMap?.size).toBe(ids.length + 1);
});
it.skip('场景一:插入 NodeSchemaid 与现有 schema 里的 id 重复,但关闭了 id 检测器', () => {
expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project;
const { nodesMap } = currentDocument;
const formNode = nodesMap.get('form');
currentDocument?.insertNode(formNode, {
const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap?.get('form');
formNode && currentDocument?.insertNode(formNode, {
componentName: 'TextInput',
id: 'nodeschema-id1',
props: {
@ -403,16 +406,16 @@ describe('schema 生成节点模型测试', () => {
propB: 3,
},
});
expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput');
expect(nodesMap.size).toBe(ids.length + 1);
expect(nodesMap?.get('nodeschema-id1')?.componentName).toBe('TextInput');
expect(nodesMap?.size).toBe(ids.length + 1);
});
it('场景二:插入 Node 实例', () => {
expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project;
const { nodesMap } = currentDocument;
const formNode = nodesMap.get('form');
const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap?.get('form');
const inputNode = currentDocument?.createNode({
componentName: 'TextInput',
id: 'nodeschema-id2',
@ -421,22 +424,22 @@ describe('schema 生成节点模型测试', () => {
propB: 3,
},
});
currentDocument?.insertNode(formNode, inputNode);
expect(formNode.children?.get(3)?.componentName).toBe('TextInput');
expect(nodesMap.size).toBe(ids.length + 1);
formNode && currentDocument?.insertNode(formNode, inputNode);
expect(formNode?.children?.get(3)?.componentName).toBe('TextInput');
expect(nodesMap?.size).toBe(ids.length + 1);
});
it('场景三:插入 JSExpression', () => {
expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project;
const { nodesMap } = currentDocument;
const formNode = nodesMap.get('form') as Node;
const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap?.get('form') as Node;
currentDocument?.insertNode(formNode, {
type: 'JSExpression',
value: 'just a expression',
});
expect(nodesMap.size).toBe(ids.length + 1);
expect(nodesMap?.size).toBe(ids.length + 1);
expect(formNode.children?.get(3)?.componentName).toBe('Leaf');
// expect(formNode.children?.get(3)?.children).toEqual({
// type: 'JSExpression',
@ -447,10 +450,10 @@ describe('schema 生成节点模型测试', () => {
expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project;
const { nodesMap } = currentDocument;
const formNode = nodesMap.get('form') as Node;
const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap?.get('form') as Node;
currentDocument?.insertNode(formNode, 'just a string');
expect(nodesMap.size).toBe(ids.length + 1);
expect(nodesMap?.size).toBe(ids.length + 1);
expect(formNode.children?.get(3)?.componentName).toBe('Leaf');
// expect(formNode.children?.get(3)?.children).toBe('just a string');
});
@ -470,8 +473,8 @@ describe('schema 生成节点模型测试', () => {
expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project;
const { nodesMap } = currentDocument;
const formNode = nodesMap.get('form') as Node;
const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap?.get('form') as Node;
const formNode2 = currentDocument?.getNode('form');
expect(formNode).toEqual(formNode2);
currentDocument?.insertNodes(formNode, [
@ -490,28 +493,28 @@ describe('schema 生成节点模型测试', () => {
},
},
], 1);
expect(nodesMap.size).toBe(ids.length + 2);
expect(nodesMap?.size).toBe(ids.length + 2);
expect(formNode.children?.length).toBe(5);
const insertedNode1 = formNode.children.get(1);
const insertedNode2 = formNode.children.get(2);
expect(insertedNode1.componentName).toBe('TextInput');
expect(insertedNode1.propsData).toEqual({
const insertedNode1 = formNode.children?.get(1);
const insertedNode2 = formNode.children?.get(2);
expect(insertedNode1?.componentName).toBe('TextInput');
expect(insertedNode1?.propsData).toEqual({
propA: 'haha2',
propB: 3,
});
expect(insertedNode2.componentName).toBe('TextInput2');
expect(insertedNode2.propsData).toEqual({
expect(insertedNode2?.componentName).toBe('TextInput2');
expect(insertedNode2?.propsData).toEqual({
propA: 'haha',
propB: 3,
});
});
it('场景二:插入 Node 实例,指定 index', () => {
it.only('场景二:插入 Node 实例,指定 index', () => {
expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project;
const { nodesMap } = currentDocument;
const formNode = nodesMap.get('form') as Node;
const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap?.get('form') as INode;
const formNode2 = currentDocument?.getNode('form');
expect(formNode).toEqual(formNode2);
const createdNode1 = currentDocument?.createNode({
@ -529,17 +532,17 @@ describe('schema 生成节点模型测试', () => {
},
});
currentDocument?.insertNodes(formNode, [createdNode1, createdNode2], 1);
expect(nodesMap.size).toBe(ids.length + 2);
expect(nodesMap?.size).toBe(ids.length + 2);
expect(formNode.children?.length).toBe(5);
const insertedNode1 = formNode.children.get(1);
const insertedNode2 = formNode.children.get(2);
expect(insertedNode1.componentName).toBe('TextInput');
expect(insertedNode1.propsData).toEqual({
const insertedNode1 = formNode.children?.get(1);
const insertedNode2 = formNode.children?.get(2);
expect(insertedNode1?.componentName).toBe('TextInput');
expect(insertedNode1?.propsData).toEqual({
propA: 'haha2',
propB: 3,
});
expect(insertedNode2.componentName).toBe('TextInput2');
expect(insertedNode2.propsData).toEqual({
expect(insertedNode2?.componentName).toBe('TextInput2');
expect(insertedNode2?.propsData).toEqual({
propA: 'haha',
propB: 3,
});
@ -558,13 +561,13 @@ describe('schema 生成节点模型测试', () => {
project.open();
expect(project).toBeTruthy();
const { currentDocument } = project;
const { nodesMap } = currentDocument;
const nodesMap = currentDocument?.nodesMap;
const ids = getIdsFromSchema(formSchema);
// 目前每个 slot 会新增1 + children.length个节点
const expectedNodeCnt = ids.length + 2;
expect(nodesMap.size).toBe(expectedNodeCnt);
expect(nodesMap?.size).toBe(expectedNodeCnt);
// PageHeader
expect(nodesMap.get('node_k1ow3cbd').slots).toHaveLength(1);
expect(nodesMap?.get('node_k1ow3cbd')?.slots).toHaveLength(1);
});
});
});

View File

@ -14,6 +14,9 @@ jest.mock('../../../src/designer/designer', () => {
getMetadata() {
return { configure: { advanced: null } };
},
get advanced() {
return {};
},
};
},
transformProps(props) { return props; },

View File

@ -16,6 +16,9 @@ jest.mock('../../../src/designer/designer', () => {
getMetadata() {
return { configure: { advanced: null } };
},
get advanced() {
return {};
},
};
},
transformProps(props) { return props; },

View File

@ -7,7 +7,6 @@ import { DocumentModel } from '../../../src/document/document-model';
import {
isRootNode,
Node,
isNode,
comparePosition,
contains,
PositionNO,
@ -23,6 +22,7 @@ import rootHeaderMetadata from '../../fixtures/component-metadata/root-header';
import rootContentMetadata from '../../fixtures/component-metadata/root-content';
import rootFooterMetadata from '../../fixtures/component-metadata/root-footer';
import { shellModelFactory } from '../../../../engine/src/modules/shell-model-factory';
import { isNode } from '@alilc/lowcode-utils';
describe('Node 方法测试', () => {
let editor: Editor;
@ -473,7 +473,7 @@ describe('Node 方法测试', () => {
const form = doc.getNode('node_k1ow3cbo');
designer.createComponentMeta(divMetadata);
designer.createComponentMeta(formMetadata);
const callbacks = form.componentMeta.getMetadata().configure.advanced?.callbacks;
const callbacks = form.componentMeta.advanced.callbacks;
const fn1 = callbacks.onNodeAdd = jest.fn();
const fn2 = callbacks.onNodeRemove = jest.fn();
const textField = doc.getNode('node_k1ow3cc9');

View File

@ -499,6 +499,59 @@ describe('Prop 类测试', () => {
expect(slotProp.purged).toBeTruthy();
slotProp.dispose();
});
describe('slotNode-value / setAsSlot', () => {
const editor = new Editor();
const designer = new Designer({ editor, shellModelFactory });
const doc = new DocumentModel(designer.project, {
componentName: 'Page',
children: [
{
id: 'div',
componentName: 'Div',
},
],
});
const div = doc.getNode('div');
const slotProp = new Prop(div?.getProps(), {
type: 'JSSlot',
value: {
componentName: 'Slot',
id: 'node_oclei5rv2e2',
props: {
slotName: "content",
slotTitle: "主内容"
},
children: [
{
componentName: 'Button',
}
]
},
});
expect(slotProp.slotNode?.componentName).toBe('Slot');
expect(slotProp.slotNode?.title).toBe('主内容');
expect(slotProp.slotNode?.getExtraProp('name')?.getValue()).toBe('content');
expect(slotProp.slotNode?.export()?.id).toBe('node_oclei5rv2e2');
slotProp.export();
// Save
expect(slotProp.export()?.value[0].componentName).toBe('Button');
expect(slotProp.export()?.title).toBe('主内容');
expect(slotProp.export()?.name).toBe('content');
// Render
expect(slotProp.export(IPublicEnumTransformStage.Render)?.value.children[0].componentName).toBe('Button');
expect(slotProp.export(IPublicEnumTransformStage.Render)?.value.componentName).toBe('Slot');
slotProp.purge();
expect(slotProp.purged).toBeTruthy();
slotProp.dispose();
});
});
describe('其他导出函数', () => {

View File

@ -17,6 +17,9 @@ jest.mock('../../src/designer/designer', () => {
getMetadata() {
return { configure: { advanced: null } };
},
get advanced() {
return {};
},
};
},
transformProps(props) { return props; },

View File

@ -17,6 +17,9 @@ jest.mock('../../src/designer/designer', () => {
getMetadata() {
return { configure: { advanced: null } };
},
get advanced() {
return {};
},
};
},
transformProps(props) { return props; },

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