chore: merge from develop

This commit is contained in:
JackLian 2023-01-30 09:06:54 +08:00
commit 7b90462109
48 changed files with 639 additions and 260 deletions

View File

@ -36,6 +36,12 @@ sidebar_position: 12
`@type {boolean}` `@type {boolean}`
### clipboard
全局剪贴板实例
`@type {IPublicModelClipboard}`
相关类型:[IPublicModelClipboard](https://github.com/alibaba/lowcode-engine/blob/main/packages/types/src/shell/model/clipboard.ts)
## 方法 ## 方法

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

@ -160,7 +160,7 @@ sidebar_position: 1
`@type {IPublicModelComponentMeta | null}` `@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 ### document
@ -644,4 +644,4 @@ isConditionalVisible(): boolean | undefined;
setConditionalVisible(): void; setConditionalVisible(): void;
``` ```
**@since v1.1.0** **@since v1.1.0**

View File

@ -1,6 +1,6 @@
--- ---
title: Resource title: Resource
sidebar_position: 12 sidebar_position: 13
--- ---
> **[@experimental](./#experimental)**<br/> > **[@experimental](./#experimental)**<br/>

View File

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

View File

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

View File

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

View File

@ -13,9 +13,9 @@ import {
IPublicTypeMetadataTransducer, IPublicTypeMetadataTransducer,
IPublicModelComponentMeta, IPublicModelComponentMeta,
} from '@alilc/lowcode-types'; } 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 { computed, createModuleEventBus, IEventBus } from '@alilc/lowcode-editor-core';
import { isNode, Node, INode } from './document'; import { Node, INode } from './document';
import { Designer } from './designer'; import { Designer } from './designer';
import { import {
IconContainer, IconContainer,
@ -161,6 +161,9 @@ export class ComponentMeta implements IComponentMeta {
return this._acceptable!; return this._acceptable!;
} }
// compatiable vision
prototype?: any;
constructor(readonly designer: Designer, metadata: IPublicTypeComponentMetadata) { constructor(readonly designer: Designer, metadata: IPublicTypeComponentMetadata) {
this.parseMetadata(metadata); this.parseMetadata(metadata);
} }
@ -347,8 +350,6 @@ export class ComponentMeta implements IComponentMeta {
}; };
} }
// compatiable vision
prototype?: any;
} }
export function isComponentMeta(obj: any): obj is ComponentMeta { export function isComponentMeta(obj: any): obj is ComponentMeta {
@ -373,4 +374,3 @@ function preprocessMetadata(metadata: IPublicTypeComponentMetadata): IPublicType
configure: {}, configure: {},
}; };
} }

View File

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

View File

@ -30,7 +30,6 @@ import { ActiveTracker, IActiveTracker } from './active-tracker';
import { Detecting } from './detecting'; import { Detecting } from './detecting';
import { DropLocation } from './location'; import { DropLocation } from './location';
import { OffsetObserver, createOffsetObserver } from './offset-observer'; import { OffsetObserver, createOffsetObserver } from './offset-observer';
import { focusing } from './focusing';
import { SettingTopEntry } from './setting'; import { SettingTopEntry } from './setting';
import { BemToolsManager } from '../builtin-simulator/bem-tools/manager'; import { BemToolsManager } from '../builtin-simulator/bem-tools/manager';
import { ComponentActions } from '../component-actions'; import { ComponentActions } from '../component-actions';
@ -241,9 +240,6 @@ export class Designer implements IDesigner {
this.postEvent('init', this); this.postEvent('init', this);
this.setupSelection(); this.setupSelection();
setupHistory(); setupHistory();
// TODO: 先简单实现,后期通过焦点赋值
focusing.focusDesigner = this;
} }
setupSelection = () => { setupSelection = () => {
@ -341,6 +337,7 @@ export class Designer implements IDesigner {
/** /**
* *
* @deprecated
*/ */
getSuitableInsertion( getSuitableInsertion(
insertNode?: INode | IPublicTypeNodeSchema | IPublicTypeNodeSchema[], insertNode?: INode | IPublicTypeNodeSchema | IPublicTypeNodeSchema[],

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 './scroller';
export * from './setting'; export * from './setting';
export * from './active-tracker'; export * from './active-tracker';
export * from './focusing';
export * from '../document'; export * from '../document';
export * from './clipboard'; export * from './clipboard';

View File

@ -17,11 +17,11 @@ import { IProject, Project } from '../project';
import { ISimulatorHost } from '../simulator'; import { ISimulatorHost } from '../simulator';
import { ComponentMeta } from '../component-meta'; import { ComponentMeta } from '../component-meta';
import { IDropLocation, Designer, IHistory } from '../designer'; import { IDropLocation, Designer, IHistory } from '../designer';
import { Node, insertChildren, insertChild, isNode, RootNode, INode } from './node/node'; import { Node, insertChildren, insertChild, RootNode, INode } from './node/node';
import { Selection, ISelection } from './selection'; import { Selection, ISelection } from './selection';
import { History } from './history'; import { History } from './history';
import { IModalNodesManager, ModalNodesManager } from './node'; import { IModalNodesManager, ModalNodesManager } from './node';
import { uniqueId, isPlainObject, compatStage, isJSExpression, isDOMText, isNodeSchema, isDragNodeObject, isDragNodeDataObject } from '@alilc/lowcode-utils'; import { uniqueId, isPlainObject, compatStage, isJSExpression, isDOMText, isNodeSchema, isDragNodeObject, isDragNodeDataObject, isNode } from '@alilc/lowcode-utils';
import { EDITOR_EVENT } from '../types'; import { EDITOR_EVENT } from '../types';
export type GetDataType<T, NodeType> = T extends undefined export type GetDataType<T, NodeType> = T extends undefined
@ -32,7 +32,7 @@ export type GetDataType<T, NodeType> = T extends undefined
: any : any
: T; : T;
export interface IDocumentModel extends Omit< IPublicModelDocumentModel, 'selection' > { export interface IDocumentModel extends Omit< IPublicModelDocumentModel, 'selection' | 'checkNesting' > {
readonly designer: Designer; readonly designer: Designer;
@ -59,6 +59,11 @@ export interface IDocumentModel extends Omit< IPublicModelDocumentModel, 'select
get rootNode(): INode | null; get rootNode(): INode | null;
checkNesting(
dropTarget: INode,
dragObject: IPublicTypeDragNodeObject | IPublicTypeNodeSchema | INode | IPublicTypeDragNodeDataObject,
): boolean;
} }
export class DocumentModel implements IDocumentModel { export class DocumentModel implements IDocumentModel {
@ -569,7 +574,10 @@ export class DocumentModel implements IDocumentModel {
this.rootNode = null; this.rootNode = null;
} }
checkNesting(dropTarget: INode, dragObject: IPublicTypeDragNodeObject | IPublicTypeNodeSchema | Node | IPublicTypeDragNodeDataObject): boolean { checkNesting(
dropTarget: INode,
dragObject: IPublicTypeDragNodeObject | IPublicTypeNodeSchema | INode | IPublicTypeDragNodeDataObject,
): boolean {
let items: Array<Node | IPublicTypeNodeSchema>; let items: Array<Node | IPublicTypeNodeSchema>;
if (isDragNodeDataObject(dragObject)) { if (isDragNodeDataObject(dragObject)) {
items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data]; items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];

View File

@ -16,7 +16,7 @@ import {
IPublicModelExclusiveGroup, IPublicModelExclusiveGroup,
IPublicEnumTransformStage, IPublicEnumTransformStage,
} from '@alilc/lowcode-types'; } from '@alilc/lowcode-types';
import { compatStage, isDOMText, isJSExpression } from '@alilc/lowcode-utils'; import { compatStage, isDOMText, isJSExpression, isNode } from '@alilc/lowcode-utils';
import { SettingTopEntry } from '@alilc/lowcode-designer'; import { SettingTopEntry } from '@alilc/lowcode-designer';
import { Props, getConvertedExtraKey, IProps } from './props/props'; import { Props, getConvertedExtraKey, IProps } from './props/props';
import { DocumentModel, IDocumentModel } from '../document-model'; import { DocumentModel, IDocumentModel } from '../document-model';
@ -109,6 +109,8 @@ export interface INode extends IPublicModelNode {
getExtraProp(key: string, createIfNone?: boolean): IProp | null; getExtraProp(key: string, createIfNone?: boolean): IProp | null;
replaceChild(node: INode, data: any): INode; replaceChild(node: INode, data: any): INode;
getSuitablePlace(node: INode, ref: any): any;
} }
/** /**
@ -913,7 +915,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/** /**
* *
*/ */
contains(node: Node): boolean { contains(node: INode): boolean {
return contains(this, node); return contains(this, node);
} }
@ -1147,16 +1149,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; const focusNode = this.document?.focusNode;
// 如果节点是模态框,插入到根节点下 // 如果节点是模态框,插入到根节点下
if (node?.componentMeta?.isModal) { if (node?.componentMeta?.isModal) {
return { container: focusNode, ref }; return { container: focusNode, ref };
} }
if (!ref && this.contains(focusNode)) { if (!ref && focusNode && this.contains(focusNode)) {
const rootCanDropIn = focusNode.componentMeta?.prototype?.options?.canDropIn; const rootCanDropIn = focusNode.componentMeta?.prototype?.options?.canDropIn;
if ( if (
rootCanDropIn === undefined || rootCanDropIn === undefined ||
@ -1171,7 +1173,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
if (this.isRoot() && this.children) { if (this.isRoot() && this.children) {
const dropElement = this.children.filter((c) => { const dropElement = this.children.filter((c) => {
if (!c.isContainer()) { if (!c.isContainerNode) {
return false; return false;
} }
const canDropIn = c.componentMeta?.prototype?.options?.canDropIn; const canDropIn = c.componentMeta?.prototype?.options?.canDropIn;
@ -1304,22 +1306,15 @@ export type PageNode = Node<IPublicTypePageSchema>;
export type ComponentNode = Node<IPublicTypeComponentSchema>; export type ComponentNode = Node<IPublicTypeComponentSchema>;
export type RootNode = PageNode | ComponentNode; export type RootNode = PageNode | ComponentNode;
/** export function isRootNode(node: INode): node is INode {
* @deprecated use same function from '@alilc/lowcode-utils' instead return node && node.isRootNode;
*/
export function isNode(node: any): node is Node {
return node && node.isNode;
} }
export function isRootNode(node: Node): node is RootNode { export function isLowCodeComponent(node: INode): node is INode {
return node && node.isRoot();
}
export function isLowCodeComponent(node: Node): boolean {
return node.componentMeta?.getMetadata().devMode === 'lowCode'; 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; let l = child.zLevel;
if (l < zLevel || zLevel < 0) { if (l < zLevel || zLevel < 0) {
return null; return null;
@ -1340,12 +1335,12 @@ export function getZLevelTop(child: Node, zLevel: number): Node | null {
* @param node2 * @param node2
* @returns * @returns
*/ */
export function contains(node1: Node, node2: Node): boolean { export function contains(node1: INode, node2: INode): boolean {
if (node1 === node2) { if (node1 === node2) {
return true; return true;
} }
if (!node1.isParental() || !node2.parent) { if (!node1.isParentalNode || !node2.parent) {
return false; return false;
} }
@ -1367,7 +1362,7 @@ export enum PositionNO {
BeforeOrAfter = 2, BeforeOrAfter = 2,
TheSame = 0, TheSame = 0,
} }
export function comparePosition(node1: Node, node2: Node): PositionNO { export function comparePosition(node1: INode, node2: INode): PositionNO {
if (node1 === node2) { if (node1 === node2) {
return PositionNO.TheSame; return PositionNO.TheSame;
} }
@ -1396,11 +1391,11 @@ export function comparePosition(node1: Node, node2: Node): PositionNO {
export function insertChild( export function insertChild(
container: INode, container: INode,
thing: Node | IPublicTypeNodeData, thing: INode | IPublicTypeNodeData,
at?: number | null, at?: number | null,
copy?: boolean, copy?: boolean,
): Node { ): INode {
let node: Node; let node: INode;
if (isNode(thing) && (copy || thing.isSlot())) { if (isNode(thing) && (copy || thing.isSlot())) {
thing = thing.export(IPublicEnumTransformStage.Clone); thing = thing.export(IPublicEnumTransformStage.Clone);
} }
@ -1410,20 +1405,20 @@ export function insertChild(
node = container.document.createNode(thing); node = container.document.createNode(thing);
} }
container.children.internalInsert(node, at); container.children.insert(node, at);
return node; return node;
} }
export function insertChildren( export function insertChildren(
container: INode, container: INode,
nodes: Node[] | IPublicTypeNodeData[], nodes: INode[] | IPublicTypeNodeData[],
at?: number | null, at?: number | null,
copy?: boolean, copy?: boolean,
): Node[] { ): INode[] {
let index = at; let index = at;
let node: any; let node: any;
const results: Node[] = []; const results: INode[] = [];
// eslint-disable-next-line no-cond-assign // eslint-disable-next-line no-cond-assign
while ((node = nodes.pop())) { while ((node = nodes.pop())) {
node = insertChild(container, node, index, copy); node = insertChild(container, node, index, copy);

View File

@ -7,7 +7,6 @@ import { DocumentModel } from '../../../src/document/document-model';
import { import {
isRootNode, isRootNode,
Node, Node,
isNode,
comparePosition, comparePosition,
contains, contains,
PositionNO, PositionNO,
@ -23,6 +22,7 @@ import rootHeaderMetadata from '../../fixtures/component-metadata/root-header';
import rootContentMetadata from '../../fixtures/component-metadata/root-content'; import rootContentMetadata from '../../fixtures/component-metadata/root-content';
import rootFooterMetadata from '../../fixtures/component-metadata/root-footer'; import rootFooterMetadata from '../../fixtures/component-metadata/root-footer';
import { shellModelFactory } from '../../../../engine/src/modules/shell-model-factory'; import { shellModelFactory } from '../../../../engine/src/modules/shell-model-factory';
import { isNode } from '@alilc/lowcode-utils';
describe('Node 方法测试', () => { describe('Node 方法测试', () => {
let editor: Editor; let editor: Editor;

View File

@ -1,8 +1,14 @@
import { IPublicTypeTransformedComponentMetadata, IPublicTypeFieldConfig, IPublicModelSettingTarget } from '@alilc/lowcode-types'; import {
IPublicTypeTransformedComponentMetadata,
IPublicTypeFieldConfig,
IPublicModelSettingTarget,
} from '@alilc/lowcode-types';
import { IconSlot } from '../icons/slot'; import { IconSlot } from '../icons/slot';
import { getConvertedExtraKey } from '@alilc/lowcode-designer'; import { getConvertedExtraKey } from '@alilc/lowcode-designer';
export default function (metadata: IPublicTypeTransformedComponentMetadata): IPublicTypeTransformedComponentMetadata { export default function (
metadata: IPublicTypeTransformedComponentMetadata,
): IPublicTypeTransformedComponentMetadata {
const { componentName, configure = {} } = metadata; const { componentName, configure = {} } = metadata;
// 如果已经处理过,不再重新执行一遍 // 如果已经处理过,不再重新执行一遍
@ -111,35 +117,33 @@ export default function (metadata: IPublicTypeTransformedComponentMetadata): IPu
}, },
]; ];
} }
/* // propsGroup.push({
propsGroup.push({ // name: '#generals',
name: '#generals', // title: { type: 'i18n', 'zh-CN': '通用', 'en-US': 'General' },
title: { type: 'i18n', 'zh-CN': '通用', 'en-US': 'General' }, // items: [
items: [ // {
{ // name: 'id',
name: 'id', // title: 'ID',
title: 'ID', // setter: 'StringSetter',
setter: 'StringSetter', // },
}, // {
{ // name: 'key',
name: 'key', // title: 'Key',
title: 'Key', // // todo: use Mixin
// todo: use Mixin // setter: 'StringSetter',
setter: 'StringSetter', // },
}, // {
{ // name: 'ref',
name: 'ref', // title: 'Ref',
title: 'Ref', // setter: 'StringSetter',
setter: 'StringSetter', // },
}, // {
{ // name: '!more',
name: '!more', // title: '更多',
title: '更多', // setter: 'PropertiesSetter',
setter: 'PropertiesSetter', // },
}, // ],
], // });
});
*/
const stylesGroup: IPublicTypeFieldConfig[] = []; const stylesGroup: IPublicTypeFieldConfig[] = [];
const advancedGroup: IPublicTypeFieldConfig[] = []; const advancedGroup: IPublicTypeFieldConfig[] = [];
if (propsGroup) { if (propsGroup) {
@ -216,18 +220,24 @@ export default function (metadata: IPublicTypeTransformedComponentMetadata): IPu
setValue(field: IPublicModelSettingTarget, eventData) { setValue(field: IPublicModelSettingTarget, eventData) {
const { eventDataList, eventList } = eventData; const { eventDataList, eventList } = eventData;
Array.isArray(eventList) && eventList.map((item) => { Array.isArray(eventList) &&
field.parent.clearPropValue(item.name); eventList.map((item) => {
return item; field.parent.clearPropValue(item.name);
}); return item;
Array.isArray(eventDataList) && eventDataList.map((item) => { });
field.parent.setPropValue(item.name, { Array.isArray(eventDataList) &&
type: 'JSFunction', eventDataList.map((item) => {
// 需要传下入参 field.parent.setPropValue(item.name, {
value: `function(){return this.${item.relatedEventName}.apply(this,Array.prototype.slice.call(arguments).concat([${item.paramStr ? item.paramStr : ''}])) }`, type: 'JSFunction',
// 需要传下入参
value: `function(){return this.${
item.relatedEventName
}.apply(this,Array.prototype.slice.call(arguments).concat([${
item.paramStr ? item.paramStr : ''
}])) }`,
});
return item;
}); });
return item;
});
}, },
}, },
], ],
@ -296,7 +306,7 @@ export default function (metadata: IPublicTypeTransformedComponentMetadata): IPu
}, },
{ {
name: 'key', name: 'key',
title: '循环 Key', title: { type: 'i18n', 'zh-CN': '循环 Key', 'en-US': 'Loop Key' },
setter: [ setter: [
{ {
componentName: 'StringSetter', componentName: 'StringSetter',
@ -317,8 +327,16 @@ export default function (metadata: IPublicTypeTransformedComponentMetadata): IPu
advancedGroup.push({ advancedGroup.push({
name: 'key', name: 'key',
title: { title: {
label: '渲染唯一标识key', label: {
tip: '搭配「条件渲染」或「循环渲染」时使用,和 react 组件中的 key 原理相同,点击查看帮助', type: 'i18n',
'zh-CN': '渲染唯一标识 (key)',
'en-US': 'Render unique identifier (key)',
},
tip: {
type: 'i18n',
'zh-CN': '搭配「条件渲染」或「循环渲染」时使用,和 react 组件中的 key 原理相同,点击查看帮助',
'en-US': 'Used with 「Conditional Rendering」or「Cycle Rendering」, the same principle as the key in the react component, click to view the help',
},
docUrl: 'https://www.yuque.com/lce/doc/qm75w3', docUrl: 'https://www.yuque.com/lce/doc/qm75w3',
}, },
setter: [ setter: [

View File

@ -1,18 +1,102 @@
/* eslint-disable max-len */ /* eslint-disable max-len */
import { isFormEvent } from '@alilc/lowcode-utils'; import { isFormEvent, isNodeSchema, isNode } from '@alilc/lowcode-utils';
import {
focusing,
insertChildren,
clipboard,
} from '@alilc/lowcode-designer';
import { import {
IPublicModelPluginContext, IPublicModelPluginContext,
IPublicEnumTransformStage, IPublicEnumTransformStage,
IPublicModelNode, IPublicModelNode,
IPublicTypeNodeSchema,
IPublicTypeNodeData,
IPublicEnumDragObjectType,
IPublicTypeDragNodeObject,
} from '@alilc/lowcode-types'; } from '@alilc/lowcode-types';
import symbols from '../modules/symbols';
const { nodeSymbol, documentSymbol } = symbols; function insertChild(
container: IPublicModelNode,
originalChild: IPublicModelNode | IPublicTypeNodeData,
at?: number | null,
): IPublicModelNode | null {
let child = originalChild;
if (isNode(child) && (child as IPublicModelNode).isSlotNode) {
child = (child as IPublicModelNode).exportSchema(IPublicEnumTransformStage.Clone);
}
let node = null;
if (isNode(child)) {
node = (child as IPublicModelNode);
container.children?.insert(node, at);
} else {
node = container.document?.createNode(child) || null;
if (node) {
container.children?.insert(node, at);
}
}
return (node as IPublicModelNode) || null;
}
function insertChildren(
container: IPublicModelNode,
nodes: IPublicModelNode[] | IPublicTypeNodeData[],
at?: number | null,
): IPublicModelNode[] {
let index = at;
let node: any;
const results: IPublicModelNode[] = [];
// eslint-disable-next-line no-cond-assign
while ((node = nodes.pop())) {
node = insertChild(container, node, index);
results.push(node);
index = node.index;
}
return results;
}
/**
*
*/
function getSuitableInsertion(
pluginContext: IPublicModelPluginContext,
insertNode?: IPublicModelNode | IPublicTypeNodeSchema | IPublicTypeNodeSchema[],
): { target: IPublicModelNode; index?: number } | null {
const { project, material } = pluginContext;
const activeDoc = project.currentDocument;
if (!activeDoc) {
return null;
}
if (
Array.isArray(insertNode) &&
isNodeSchema(insertNode[0]) &&
material.getComponentMeta(insertNode[0].componentName)?.isModal
) {
if (!activeDoc.root) {
return null;
}
return {
target: activeDoc.root,
};
}
const focusNode = activeDoc.focusNode!;
const nodes = activeDoc.selection.getNodes();
const refNode = nodes.find((item) => focusNode.contains(item));
let target;
let index: number | undefined;
if (!refNode || refNode === focusNode) {
target = focusNode;
} else if (refNode.componentMeta?.isContainer) {
target = refNode;
} else {
// FIXME!!, parent maybe null
target = refNode.parent!;
index = refNode.index + 1;
}
if (target && insertNode && !target.componentMeta?.checkNestingDown(target, insertNode)) {
return null;
}
return { target, index };
}
/* istanbul ignore next */ /* istanbul ignore next */
function getNextForSelect(next: IPublicModelNode | null, head?: any, parent?: IPublicModelNode | null): any { function getNextForSelect(next: IPublicModelNode | null, head?: any, parent?: IPublicModelNode | null): any {
@ -76,11 +160,73 @@ function getPrevForSelect(prev: IPublicModelNode | null, head?: any, parent?: IP
return null; return null;
} }
function getSuitablePlaceForNode(targetNode: IPublicModelNode, node: IPublicModelNode, ref: any): any {
const { document } = targetNode;
if (!document) {
return null;
}
const dragNodeObject: IPublicTypeDragNodeObject = {
type: IPublicEnumDragObjectType.Node,
nodes: [node],
};
const focusNode = document?.focusNode;
// 如果节点是模态框,插入到根节点下
if (node?.componentMeta?.isModal) {
return { container: focusNode, ref };
}
const canDropInFn = document.checkNesting;
if (!ref && focusNode && targetNode.contains(focusNode)) {
if (canDropInFn(focusNode, dragNodeObject)) {
return { container: focusNode };
}
return null;
}
if (targetNode.isRootNode && targetNode.children) {
const dropElement = targetNode.children.filter((c) => {
if (!c.isContainerNode) {
return false;
}
if (canDropInFn(c, dragNodeObject)) {
return true;
}
return false;
})[0];
if (dropElement) {
return { container: dropElement, ref };
}
if (canDropInFn(targetNode, dragNodeObject)) {
return { container: targetNode, ref };
}
return null;
}
if (targetNode.isContainerNode) {
if (canDropInFn(targetNode, dragNodeObject)) {
return { container: targetNode, ref };
}
}
if (targetNode.parent) {
return getSuitablePlaceForNode(targetNode.parent, node, { index: targetNode.index });
}
return null;
}
// 注册默认的 setters // 注册默认的 setters
export const builtinHotkey = (ctx: IPublicModelPluginContext) => { export const builtinHotkey = (ctx: IPublicModelPluginContext) => {
return { return {
init() { init() {
const { hotkey, project, logger, canvas } = ctx; const { hotkey, project, logger, canvas } = ctx;
const { clipboard } = canvas;
// hotkey binding // hotkey binding
hotkey.bind(['backspace', 'del'], (e: KeyboardEvent, action) => { hotkey.bind(['backspace', 'del'], (e: KeyboardEvent, action) => {
logger.info(`action ${action} is triggered`); logger.info(`action ${action} is triggered`);
@ -108,11 +254,11 @@ export const builtinHotkey = (ctx: IPublicModelPluginContext) => {
hotkey.bind('escape', (e: KeyboardEvent, action) => { hotkey.bind('escape', (e: KeyboardEvent, action) => {
logger.info(`action ${action} is triggered`); logger.info(`action ${action} is triggered`);
// const currentFocus = focusing.current;
if (canvas.isInLiveEditing) { if (canvas.isInLiveEditing) {
return; return;
} }
const sel = focusing.focusDesigner?.currentDocument?.selection; const sel = project.currentDocument?.selection;
if (isFormEvent(e) || !sel) { if (isFormEvent(e) || !sel) {
return; return;
} }
@ -168,26 +314,31 @@ export const builtinHotkey = (ctx: IPublicModelPluginContext) => {
return; return;
} }
// TODO // TODO
const designer = focusing.focusDesigner;
const doc = project?.currentDocument; const doc = project?.currentDocument;
if (isFormEvent(e) || !designer || !doc) { if (isFormEvent(e) || !doc) {
return; return;
} }
/* istanbul ignore next */ /* istanbul ignore next */
clipboard.waitPasteData(e, ({ componentsTree }) => { clipboard.waitPasteData(e, ({ componentsTree }) => {
if (componentsTree) { if (componentsTree) {
const { target, index } = designer.getSuitableInsertion(componentsTree) || {}; const { target, index } = getSuitableInsertion(ctx, componentsTree) || {};
if (!target) { if (!target) {
return; return;
} }
let canAddComponentsTree = componentsTree.filter((i) => { let canAddComponentsTree = componentsTree.filter((node: IPublicModelNode) => {
return (doc as any)[documentSymbol].checkNestingUp(target, i); const dragNodeObject: IPublicTypeDragNodeObject = {
type: IPublicEnumDragObjectType.Node,
nodes: [node],
};
return doc.checkNesting(target, dragNodeObject);
}); });
if (canAddComponentsTree.length === 0) return; if (canAddComponentsTree.length === 0) {
return;
}
const nodes = insertChildren(target, canAddComponentsTree, index); const nodes = insertChildren(target, canAddComponentsTree, index);
if (nodes) { if (nodes) {
doc.selection.selectAll(nodes.map((o) => o.id)); doc.selection.selectAll(nodes.map((o) => o.id));
setTimeout(() => designer.activeTracker.track(nodes[0]), 10); setTimeout(() => canvas.activeTracker?.track(nodes[0]), 10);
} }
} }
}); });
@ -333,14 +484,14 @@ export const builtinHotkey = (ctx: IPublicModelPluginContext) => {
const silbing = firstNode.prevSibling; const silbing = firstNode.prevSibling;
if (silbing) { if (silbing) {
if (silbing.isContainerNode) { if (silbing.isContainerNode) {
const place = (silbing as any)[nodeSymbol].getSuitablePlace(firstNode, null); const place = getSuitablePlaceForNode(silbing, firstNode, null);
silbing.insertAfter(firstNode, place.ref, true); silbing.insertAfter(firstNode, place.ref, true);
} else { } else {
parent.insertBefore(firstNode, silbing, true); parent.insertBefore(firstNode, silbing, true);
} }
firstNode?.select(); firstNode?.select();
} else { } else {
const place = (parent as any)[nodeSymbol].getSuitablePlace(firstNode, null); // upwards const place = getSuitablePlaceForNode(parent, firstNode, null); // upwards
if (place) { if (place) {
const container = place.container.internalToShellNode(); const container = place.container.internalToShellNode();
container.insertBefore(firstNode, place.ref); container.insertBefore(firstNode, place.ref);
@ -381,7 +532,7 @@ export const builtinHotkey = (ctx: IPublicModelPluginContext) => {
} }
firstNode?.select(); firstNode?.select();
} else { } else {
const place = (parent as any)[nodeSymbol].getSuitablePlace(firstNode, null); // upwards const place = getSuitablePlaceForNode(parent, firstNode, null); // upwards
if (place) { if (place) {
const container = place.container.internalToShellNode(); const container = place.container.internalToShellNode();
container.insertAfter(firstNode, place.ref, true); container.insertAfter(firstNode, place.ref, true);

View File

@ -11,5 +11,12 @@
"Slots": "Slots", "Slots": "Slots",
"Slot for {prop}": "Slot for {prop}", "Slot for {prop}": "Slot for {prop}",
"Outline Tree": "Outline Tree", "Outline Tree": "Outline Tree",
"Filter Node": "Filter Node",
"Check All": "Check All",
"Conditional rendering": "Conditional rendering",
"Loop rendering": "Loop rendering",
"Locked": "Locked",
"Hidden": "Hidden",
"Modal View": "Modal View",
"Rename": "Rename" "Rename": "Rename"
} }

View File

@ -11,5 +11,12 @@
"Slots": "插槽", "Slots": "插槽",
"Slot for {prop}": "属性 {prop} 的插槽", "Slot for {prop}": "属性 {prop} 的插槽",
"Outline Tree": "大纲树", "Outline Tree": "大纲树",
"Filter Node": "过滤节点",
"Check All": "全选",
"Conditional rendering": "条件渲染",
"Loop rendering": "循环渲染",
"Locked": "已锁定",
"Hidden": "已隐藏",
"Modal View": "模态视图层",
"Rename": "重命名" "Rename": "重命名"
} }

View File

@ -9,16 +9,16 @@ export const FilterType = {
export const FILTER_OPTIONS = [{ export const FILTER_OPTIONS = [{
value: FilterType.CONDITION, value: FilterType.CONDITION,
label: '条件渲染', label: 'Conditional rendering',
}, { }, {
value: FilterType.LOOP, value: FilterType.LOOP,
label: '循环渲染', label: 'Loop rendering',
}, { }, {
value: FilterType.LOCKED, value: FilterType.LOCKED,
label: '已锁定', label: 'Locked',
}, { }, {
value: FilterType.HIDDEN, value: FilterType.HIDDEN,
label: '已隐藏', label: 'Hidden',
}]; }];
export const matchTreeNode = ( export const matchTreeNode = (

View File

@ -5,10 +5,11 @@ import { Search, Checkbox, Balloon, Divider } from '@alifd/next';
import TreeNode from '../controllers/tree-node'; import TreeNode from '../controllers/tree-node';
import { Tree } from '../controllers/tree'; import { Tree } from '../controllers/tree';
import { matchTreeNode, FILTER_OPTIONS } from './filter-tree'; import { matchTreeNode, FILTER_OPTIONS } from './filter-tree';
import { IPublicModelPluginContext } from '@alilc/lowcode-types';
export default class Filter extends Component<{ export default class Filter extends Component<{
tree: Tree; tree: Tree;
pluginContext: IPublicModelPluginContext;
}, { }, {
keywords: string; keywords: string;
filterOps: string[]; filterOps: string[];
@ -55,7 +56,7 @@ export default class Filter extends Component<{
<Search <Search
hasClear hasClear
shape="simple" shape="simple"
placeholder="过滤节点" placeholder={this.props.pluginContext.intl('Filter Node')}
className="lc-outline-filter-search-input" className="lc-outline-filter-search-input"
value={keywords} value={keywords}
onChange={this.handleSearchChange} onChange={this.handleSearchChange}
@ -76,7 +77,7 @@ export default class Filter extends Component<{
indeterminate={indeterminate} indeterminate={indeterminate}
onChange={this.handleCheckAll} onChange={this.handleCheckAll}
> >
{this.props.pluginContext.intlNode('Check All')}
</Checkbox> </Checkbox>
<Divider /> <Divider />
<Checkbox.Group <Checkbox.Group
@ -90,7 +91,7 @@ export default class Filter extends Component<{
value={op.value} value={op.value}
key={op.value} key={op.value}
> >
{op.label} {this.props.pluginContext.intlNode(op.label)}
</Checkbox> </Checkbox>
))} ))}
</Checkbox.Group> </Checkbox.Group>

View File

@ -6,7 +6,6 @@ import { IPublicModelPluginContext } from '@alilc/lowcode-types';
import Filter from './filter'; import Filter from './filter';
import { TreeMaster } from '../controllers/tree-master'; import { TreeMaster } from '../controllers/tree-master';
export class Pane extends Component<{ export class Pane extends Component<{
config: any; config: any;
pluginContext: IPublicModelPluginContext; pluginContext: IPublicModelPluginContext;
@ -40,7 +39,7 @@ export class Pane extends Component<{
return ( return (
<div className="lc-outline-pane"> <div className="lc-outline-pane">
<Filter tree={tree} /> <Filter tree={tree} pluginContext={this.props.pluginContext} />
<div ref={(shell) => this.controller.mount(shell)} className="lc-outline-tree-container"> <div ref={(shell) => this.controller.mount(shell)} className="lc-outline-tree-container">
<TreeView key={tree.id} tree={tree} pluginContext={this.props.pluginContext} /> <TreeView key={tree.id} tree={tree} pluginContext={this.props.pluginContext} />
</div> </div>

View File

@ -38,7 +38,7 @@ class ModalTreeNodeView extends Component<{
return ( return (
<div className="tree-node-modal"> <div className="tree-node-modal">
<div className="tree-node-modal-title"> <div className="tree-node-modal-title">
<span></span> <span>{this.pluginContext.intlNode('Modal View')}</span>
<div <div
className="tree-node-modal-title-visible-icon" className="tree-node-modal-title-visible-icon"
onClick={this.hideAllNodes.bind(this)} onClick={this.hideAllNodes.bind(this)}

View File

@ -0,0 +1,4 @@
{
"Drag and drop components or templates here": "Drag and drop components or templates here",
"Locked elements and child elements cannot be edited": "Locked elements and child elements cannot be edited"
}

View File

@ -0,0 +1,21 @@
import { createElement } from 'react';
import enUS from './en-US.json';
import zhCN from './zh-CN.json';
const instance: Record<string, Record<string, string>> = {
'zh-CN': zhCN as Record<string, string>,
'en-US': enUS as Record<string, string>,
};
export function createIntl(locale: string = 'zh-CN') {
const intl = (id: string) => {
return instance[locale][id];
};
const intlNode = (id: string) => createElement('span', instance[locale][id]);
return {
intl,
intlNode,
};
}

View File

@ -0,0 +1,4 @@
{
"Drag and drop components or templates here": "拖拽组件或模板到这里",
"Locked elements and child elements cannot be edited": "锁定元素及子元素无法编辑"
}

View File

@ -10,6 +10,7 @@ import { SimulatorRendererContainer, DocumentInstance } from './renderer';
import { host } from './host'; import { host } from './host';
import { isRendererDetached } from './utils/misc'; import { isRendererDetached } from './utils/misc';
import './renderer.less'; import './renderer.less';
import { createIntl } from './locale';
// patch cloneElement avoid lost keyProps // patch cloneElement avoid lost keyProps
const originCloneElement = window.React.cloneElement; const originCloneElement = window.React.cloneElement;
@ -130,6 +131,7 @@ class Renderer extends Component<{
documentInstance: DocumentInstance; documentInstance: DocumentInstance;
}> { }> {
startTime: number | null = null; startTime: number | null = null;
schemaChangedSymbol = false;
componentDidUpdate() { componentDidUpdate() {
this.recordTime(); this.recordTime();
@ -152,8 +154,6 @@ class Renderer extends Component<{
this.recordTime(); this.recordTime();
} }
schemaChangedSymbol = false;
getSchemaChangedSymbol = () => { getSchemaChangedSymbol = () => {
return this.schemaChangedSymbol; return this.schemaChangedSymbol;
}; };
@ -172,6 +172,8 @@ class Renderer extends Component<{
if (!container.autoRender || isRendererDetached()) return null; if (!container.autoRender || isRendererDetached()) return null;
const { intl } = createIntl(locale);
return ( return (
<LowCodeRenderer <LowCodeRenderer
locale={locale} locale={locale}
@ -206,12 +208,12 @@ class Renderer extends Component<{
(children == null || (Array.isArray(children) && !children.length)) && (children == null || (Array.isArray(children) && !children.length)) &&
(!viewProps.style || Object.keys(viewProps.style).length === 0) (!viewProps.style || Object.keys(viewProps.style).length === 0)
) { ) {
let defaultPlaceholder = '拖拽组件或模板到这里'; let defaultPlaceholder = intl('Drag and drop components or templates here');
const lockedNode = getClosestNode(leaf, (node) => { const lockedNode = getClosestNode(leaf, (node) => {
return node?.getExtraProp('isLocked')?.getValue() === true; return node?.getExtraProp('isLocked')?.getValue() === true;
}); });
if (lockedNode) { if (lockedNode) {
defaultPlaceholder = '锁定元素及子元素无法编辑'; defaultPlaceholder = intl('Locked elements and child elements cannot be edited');
} }
children = ( children = (
<div className={cn('lc-container-placeholder', { 'lc-container-locked': !!lockedNode })} style={viewProps.placeholderStyle}> <div className={cn('lc-container-placeholder', { 'lc-container-locked': !!lockedNode })} style={viewProps.placeholderStyle}>

View File

@ -8,6 +8,7 @@ import {
IPublicModelEditor, IPublicModelEditor,
IPublicModelDragon, IPublicModelDragon,
IPublicModelActiveTracker, IPublicModelActiveTracker,
IPublicModelClipboard,
} from '@alilc/lowcode-types'; } from '@alilc/lowcode-types';
import { import {
ScrollTarget as InnerScrollTarget, ScrollTarget as InnerScrollTarget,
@ -18,10 +19,14 @@ import {
Dragon as ShellDragon, Dragon as ShellDragon,
DropLocation as ShellDropLocation, DropLocation as ShellDropLocation,
ActiveTracker as ShellActiveTracker, ActiveTracker as ShellActiveTracker,
Clipboard as ShellClipboard,
} from '../model'; } from '../model';
const clipboardInstanceSymbol = Symbol('clipboardInstace');
export class Canvas implements IPublicApiCanvas { export class Canvas implements IPublicApiCanvas {
private readonly [editorSymbol]: IPublicModelEditor; private readonly [editorSymbol]: IPublicModelEditor;
private readonly [clipboardInstanceSymbol]: IPublicModelClipboard;
private get [designerSymbol](): IDesigner { private get [designerSymbol](): IDesigner {
return this[editorSymbol].get('designer') as IDesigner; return this[editorSymbol].get('designer') as IDesigner;
@ -40,8 +45,13 @@ export class Canvas implements IPublicApiCanvas {
return Boolean(this[editorSymbol].get('designer')?.project?.simulator?.liveEditing?.editing); return Boolean(this[editorSymbol].get('designer')?.project?.simulator?.liveEditing?.editing);
} }
get clipboard(): IPublicModelClipboard {
return this[clipboardInstanceSymbol];
}
constructor(editor: IPublicModelEditor, readonly workspaceMode: boolean = false) { constructor(editor: IPublicModelEditor, readonly workspaceMode: boolean = false) {
this[editorSymbol] = editor; this[editorSymbol] = editor;
this[clipboardInstanceSymbol] = new ShellClipboard();
} }
createScrollTarget(shell: HTMLDivElement): IPublicModelScrollTarget { createScrollTarget(shell: HTMLDivElement): IPublicModelScrollTarget {

View File

@ -9,6 +9,7 @@ import {
Dragon, Dragon,
SettingPropEntry, SettingPropEntry,
SettingTopEntry, SettingTopEntry,
Clipboard,
} from './model'; } from './model';
import { import {
Project, Project,
@ -57,4 +58,5 @@ export {
Logger, Logger,
Canvas, Canvas,
Workspace, Workspace,
Clipboard,
}; };

View File

@ -0,0 +1,22 @@
import { IPublicModelClipboard } from '@alilc/lowcode-types';
import { clipboardSymbol } from '../symbols';
import { IClipboard, clipboard } from '@alilc/lowcode-designer';
export class Clipboard implements IPublicModelClipboard {
private readonly [clipboardSymbol]: IClipboard;
constructor() {
this[clipboardSymbol] = clipboard;
}
setData(data: any): void {
this[clipboardSymbol].setData(data);
}
waitPasteData(
keyboardEvent: KeyboardEvent,
cb: (data: any, clipboardEvent: ClipboardEvent) => void,
): void {
this[clipboardSymbol].waitPasteData(keyboardEvent, cb);
}
}

View File

@ -8,7 +8,6 @@ import {
GlobalEvent, GlobalEvent,
IPublicModelDocumentModel, IPublicModelDocumentModel,
IPublicTypeOnChangeOptions, IPublicTypeOnChangeOptions,
IPublicModelDragObject,
IPublicTypeDragNodeObject, IPublicTypeDragNodeObject,
IPublicTypeDragNodeDataObject, IPublicTypeDragNodeDataObject,
IPublicModelNode, IPublicModelNode,
@ -227,9 +226,11 @@ export class DocumentModel implements IPublicModelDocumentModel {
dropTarget: IPublicModelNode, dropTarget: IPublicModelNode,
dragObject: IPublicTypeDragNodeObject | IPublicTypeDragNodeDataObject, dragObject: IPublicTypeDragNodeObject | IPublicTypeDragNodeDataObject,
): boolean { ): boolean {
let innerDragObject: IPublicModelDragObject = dragObject; let innerDragObject = dragObject;
if (isDragNodeObject(dragObject)) { if (isDragNodeObject(dragObject)) {
innerDragObject.nodes = innerDragObject.nodes.map((node: Node) => (node[nodeSymbol] || node)); innerDragObject.nodes = innerDragObject.nodes?.map(
(node: IPublicModelNode) => ((node as any)[nodeSymbol] || node),
);
} }
return this[documentSymbol].checkNesting( return this[documentSymbol].checkNesting(
((dropTarget as any)[nodeSymbol] || dropTarget) as any, ((dropTarget as any)[nodeSymbol] || dropTarget) as any,

View File

@ -17,4 +17,5 @@ export * from './setting-top-entry';
export * from './resource'; export * from './resource';
export * from './active-tracker'; export * from './active-tracker';
export * from './plugin-instance'; export * from './plugin-instance';
export * from './window'; export * from './window';
export * from './clipboard';

View File

@ -30,4 +30,5 @@ export const workspaceSymbol = Symbol('workspace');
export const windowSymbol = Symbol('window'); export const windowSymbol = Symbol('window');
export const pluginInstanceSymbol = Symbol('plugin-instance'); export const pluginInstanceSymbol = Symbol('plugin-instance');
export const resourceTypeSymbol = Symbol('resourceType'); export const resourceTypeSymbol = Symbol('resourceType');
export const resourceSymbol = Symbol('resource'); export const resourceSymbol = Symbol('resource');
export const clipboardSymbol = Symbol('clipboard');

View File

@ -1,4 +1,4 @@
import { IPublicModelDragon, IPublicModelDropLocation, IPublicModelScrollTarget, IPublicModelScrollable, IPublicModelScroller, IPublicModelActiveTracker } from '../model'; import { IPublicModelDragon, IPublicModelDropLocation, IPublicModelScrollTarget, IPublicModelScrollable, IPublicModelScroller, IPublicModelActiveTracker, IPublicModelClipboard } from '../model';
import { IPublicTypeLocationData } from '../type'; import { IPublicTypeLocationData } from '../type';
/** /**
@ -54,4 +54,12 @@ export interface IPublicApiCanvas {
* @since v1.1.0 * @since v1.1.0
*/ */
get isInLiveEditing(): boolean; get isInLiveEditing(): boolean;
/**
*
* get clipboard instance
*
* @since v1.1.0
*/
get clipboard(): IPublicModelClipboard;
} }

View File

@ -0,0 +1,25 @@
export interface IPublicModelClipboard {
/**
*
* set data to clipboard
*
* @param {*} data
* @since v1.1.0
*/
setData(data: any): void;
/**
*
* 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

@ -29,3 +29,4 @@ export * from './preference';
export * from './plugin-instance'; export * from './plugin-instance';
export * from './sensor'; export * from './sensor';
export * from './resource'; export * from './resource';
export * from './clipboard';

View File

@ -1,7 +1,7 @@
export interface IPublicResourceData { export interface IPublicResourceData {
resourceName: string; resourceName: string;
title: string; title: string;
category: string; category?: string;
options: { options: {
[key: string]: any; [key: string]: any;
}; };

View File

@ -1,5 +1,3 @@
/* eslint-disable no-param-reassign */
/* eslint-disable max-len */
import { import {
Editor, Editor,
engineConfig, Setters as InnerSetters, engineConfig, Setters as InnerSetters,
@ -33,8 +31,8 @@ import {
IPublicTypePluginMeta, IPublicTypePluginMeta,
} from '@alilc/lowcode-types'; } from '@alilc/lowcode-types';
import { getLogger } from '@alilc/lowcode-utils'; import { getLogger } from '@alilc/lowcode-utils';
import { Workspace as InnerWorkspace } from './workspace'; import { Workspace as InnerWorkspace } from '../workspace';
import { EditorWindow } from './editor-window/context'; import { EditorWindow } from '../window';
export class BasicContext { export class BasicContext {
skeleton: Skeleton; skeleton: Skeleton;

View File

@ -2,8 +2,8 @@ import { makeObservable, obx } from '@alilc/lowcode-editor-core';
import { IPublicEditorViewConfig, IPublicTypeEditorView } from '@alilc/lowcode-types'; import { IPublicEditorViewConfig, IPublicTypeEditorView } from '@alilc/lowcode-types';
import { flow } from 'mobx'; import { flow } from 'mobx';
import { Workspace as InnerWorkspace } from '../workspace'; import { Workspace as InnerWorkspace } from '../workspace';
import { BasicContext } from '../base-context'; import { BasicContext } from './base-context';
import { EditorWindow } from '../editor-window/context'; import { EditorWindow } from '../window';
import { getWebviewPlugin } from '../inner-plugins/webview'; import { getWebviewPlugin } from '../inner-plugins/webview';
export class Context extends BasicContext { export class Context extends BasicContext {
@ -17,6 +17,10 @@ export class Context extends BasicContext {
@obx isInit: boolean = false; @obx isInit: boolean = false;
get active() {
return this._activate;
}
init = flow(function* (this: any) { init = flow(function* (this: any) {
if (this.viewType === 'webview') { if (this.viewType === 'webview') {
const url = yield this.instance?.url?.(); const url = yield this.instance?.url?.();
@ -44,10 +48,6 @@ export class Context extends BasicContext {
this.innerHotkey.activate(this._activate); this.innerHotkey.activate(this._activate);
}; };
get active() {
return this._activate;
}
async save() { async save() {
return await this.instance?.save?.(); return await this.instance?.save?.();
} }

View File

@ -1,4 +1,4 @@
export { Workspace } from './workspace'; export { Workspace } from './workspace';
export * from './editor-window/context'; export * from './window';
export * from './layouts/workbench'; export * from './layouts/workbench';
export { Resource } from './resource'; export { Resource } from './resource';

View File

@ -1,6 +1,6 @@
import { Component } from 'react'; import { Component } from 'react';
import { TipContainer, observer } from '@alilc/lowcode-editor-core'; import { TipContainer, observer } from '@alilc/lowcode-editor-core';
import { EditorWindowView } from '../editor-window/view'; import { WindowView } from '../view/window-view';
import classNames from 'classnames'; import classNames from 'classnames';
import TopArea from './top-area'; import TopArea from './top-area';
import LeftArea from './left-area'; import LeftArea from './left-area';
@ -46,9 +46,9 @@ export class Workbench extends Component<{
<div className="lc-workspace-workbench-window"> <div className="lc-workspace-workbench-window">
{ {
workspace.windows.map(d => ( workspace.windows.map(d => (
<EditorWindowView <WindowView
active={d.id === workspace.window.id} active={d.id === workspace.window.id}
editorWindow={d} window={d}
key={d.id} key={d.id}
/> />
)) ))

View File

@ -1,6 +1,6 @@
import { IPublicTypeEditorView, IPublicModelResource, IPublicResourceData, IPublicResourceTypeConfig } from '@alilc/lowcode-types'; import { IPublicTypeEditorView, IPublicModelResource, IPublicResourceData, IPublicResourceTypeConfig } from '@alilc/lowcode-types';
import { Logger } from '@alilc/lowcode-utils'; import { Logger } from '@alilc/lowcode-utils';
import { BasicContext } from './base-context'; import { BasicContext } from './context/base-context';
import { ResourceType } from './resource-type'; import { ResourceType } from './resource-type';
import { Workspace as InnerWorkSpace } from './workspace'; import { Workspace as InnerWorkSpace } from './workspace';
@ -13,20 +13,6 @@ export class Resource implements IPublicModelResource {
editorViewMap: Map<string, IPublicTypeEditorView> = new Map<string, IPublicTypeEditorView>(); editorViewMap: Map<string, IPublicTypeEditorView> = new Map<string, IPublicTypeEditorView>();
constructor(readonly resourceData: IPublicResourceData, readonly resourceType: ResourceType, workspace: InnerWorkSpace) {
this.context = new BasicContext(workspace, '');
this.resourceTypeInstance = resourceType.resourceTypeModel(this.context, {});
this.init();
if (this.resourceTypeInstance.editorViews) {
this.resourceTypeInstance.editorViews.forEach((d: any) => {
this.editorViewMap.set(d.viewName, d);
});
}
if (!resourceType) {
logger.error(`resourceType[${resourceType}] is unValid.`);
}
}
get name() { get name() {
return this.resourceType.name; return this.resourceType.name;
} }
@ -55,6 +41,24 @@ export class Resource implements IPublicModelResource {
return this.resourceData?.category; return this.resourceData?.category;
} }
get skeleton() {
return this.context.innerSkeleton;
}
constructor(readonly resourceData: IPublicResourceData, readonly resourceType: ResourceType, workspace: InnerWorkSpace) {
this.context = new BasicContext(workspace, `resource-${resourceData.resourceName || resourceType.name}`);
this.resourceTypeInstance = resourceType.resourceTypeModel(this.context, {});
this.init();
if (this.resourceTypeInstance.editorViews) {
this.resourceTypeInstance.editorViews.forEach((d: any) => {
this.editorViewMap.set(d.viewName, d);
});
}
if (!resourceType) {
logger.error(`resourceType[${resourceType}] is unValid.`);
}
}
async init() { async init() {
await this.resourceTypeInstance.init?.(); await this.resourceTypeInstance.init?.();
await this.context.innerPlugins.init(); await this.context.innerPlugins.init();
@ -63,6 +67,7 @@ export class Resource implements IPublicModelResource {
async import(schema: any) { async import(schema: any) {
return await this.resourceTypeInstance.import?.(schema); return await this.resourceTypeInstance.import?.(schema);
} }
async save(value: any) { async save(value: any) {
return await this.resourceTypeInstance.save?.(value); return await this.resourceTypeInstance.save?.(value);
} }

View File

@ -4,9 +4,9 @@ import {
Workbench, Workbench,
} from '@alilc/lowcode-editor-skeleton'; } from '@alilc/lowcode-editor-skeleton';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { Context } from './context'; import { Context } from '../context/view-context';
export * from '../base-context'; export * from '../context/base-context';
@observer @observer
export class EditorView extends PureComponent<{ export class EditorView extends PureComponent<{
@ -23,13 +23,11 @@ export class EditorView extends PureComponent<{
} }
return ( return (
<> <Workbench
<Workbench skeleton={skeleton}
skeleton={skeleton} className={active ? 'active engine-editor-view' : 'engine-editor-view'}
className={active ? 'active engine-editor-view' : 'engine-editor-view'} topAreaItemClassName="engine-actionitem"
topAreaItemClassName="engine-actionitem" />
/>
</>
); );
} }
} }

View File

@ -0,0 +1,14 @@
.workspace-resource-view {
display: flex;
position: absolute;
flex-direction: column;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.workspace-editor-body {
position: relative;
height: 100%;
}

View File

@ -0,0 +1,36 @@
import { PureComponent } from 'react';
import { EditorView } from './editor-view';
import { observer } from '@alilc/lowcode-editor-core';
import TopArea from '../layouts/top-area';
import { Resource } from '../resource';
import { EditorWindow } from '../window';
import './resource-view.less';
@observer
export class ResourceView extends PureComponent<{
window: EditorWindow;
resource: Resource;
}, any> {
render() {
const { skeleton } = this.props.resource;
const { editorViews } = this.props.window;
return (
<div className="workspace-resource-view">
<TopArea area={skeleton.topArea} itemClassName="engine-actionitem" />
<div className="workspace-editor-body">
{
Array.from(editorViews.values()).map((editorView: any) => {
return (
<EditorView
key={editorView.name}
active={editorView.active}
editorView={editorView}
/>
);
})
}
</div>
</div>
);
}
}

View File

@ -1,17 +1,17 @@
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { EditorView } from '../editor-view/view'; import { ResourceView } from './resource-view';
import { engineConfig, observer } from '@alilc/lowcode-editor-core'; import { engineConfig, observer } from '@alilc/lowcode-editor-core';
import { EditorWindow } from './context'; import { EditorWindow } from '../window';
import { BuiltinLoading } from '@alilc/lowcode-designer'; import { BuiltinLoading } from '@alilc/lowcode-designer';
@observer @observer
export class EditorWindowView extends PureComponent<{ export class WindowView extends PureComponent<{
editorWindow: EditorWindow; window: EditorWindow;
active: boolean; active: boolean;
}, any> { }, any> {
render() { render() {
const { active } = this.props; const { active } = this.props;
const { editorView, editorViews } = this.props.editorWindow; const { editorView, resource } = this.props.window;
if (!editorView) { if (!editorView) {
const Loading = engineConfig.get('loadingComponent', BuiltinLoading); const Loading = engineConfig.get('loadingComponent', BuiltinLoading);
return ( return (
@ -23,17 +23,10 @@ export class EditorWindowView extends PureComponent<{
return ( return (
<div className={`workspace-engine-main ${active ? 'active' : ''}`}> <div className={`workspace-engine-main ${active ? 'active' : ''}`}>
{ <ResourceView
Array.from(editorViews.values()).map((editorView: any) => { resource={resource}
return ( window={this.props.window}
<EditorView />
key={editorView.name}
active={editorView.active}
editorView={editorView}
/>
);
})
}
</div> </div>
); );
} }

View File

@ -1,8 +1,8 @@
import { uniqueId } from '@alilc/lowcode-utils'; import { uniqueId } from '@alilc/lowcode-utils';
import { makeObservable, obx } from '@alilc/lowcode-editor-core'; import { makeObservable, obx } from '@alilc/lowcode-editor-core';
import { Context } from '../editor-view/context'; import { Context } from './context/view-context';
import { Workspace } from '../workspace'; import { Workspace } from './workspace';
import { Resource } from '../resource'; import { Resource } from './resource';
export class EditorWindow { export class EditorWindow {
id: string = uniqueId('window'); id: string = uniqueId('window');

View File

@ -2,8 +2,8 @@ import { Designer } from '@alilc/lowcode-designer';
import { createModuleEventBus, Editor, IEventBus, makeObservable, obx } from '@alilc/lowcode-editor-core'; import { createModuleEventBus, Editor, IEventBus, makeObservable, obx } from '@alilc/lowcode-editor-core';
import { Plugins } from '@alilc/lowcode-shell'; import { Plugins } from '@alilc/lowcode-shell';
import { IPublicApiWorkspace, IPublicResourceList, IPublicTypeResourceType } from '@alilc/lowcode-types'; import { IPublicApiWorkspace, IPublicResourceList, IPublicTypeResourceType } from '@alilc/lowcode-types';
import { BasicContext } from './base-context'; import { BasicContext } from './context/base-context';
import { EditorWindow } from './editor-window/context'; import { EditorWindow } from './window';
import { Resource } from './resource'; import { Resource } from './resource';
import { ResourceType } from './resource-type'; import { ResourceType } from './resource-type';
@ -20,6 +20,12 @@ export class Workspace implements IPublicApiWorkspace {
private emitter: IEventBus = createModuleEventBus('workspace'); private emitter: IEventBus = createModuleEventBus('workspace');
private _isActive = false;
private resourceTypeMap: Map<string, ResourceType> = new Map();
private resourceList: Resource[] = [];
get skeleton() { get skeleton() {
return this.context.innerSkeleton; return this.context.innerSkeleton;
} }
@ -28,7 +34,17 @@ export class Workspace implements IPublicApiWorkspace {
return this.context.innerPlugins; return this.context.innerPlugins;
} }
private _isActive = false; get isActive() {
return this._isActive;
}
get defaultResourceType(): ResourceType | null {
if (this.resourceTypeMap.size >= 1) {
return Array.from(this.resourceTypeMap.values())[0];
}
return null;
}
windows: EditorWindow[] = []; windows: EditorWindow[] = [];
@ -36,10 +52,6 @@ export class Workspace implements IPublicApiWorkspace {
@obx.ref window: EditorWindow; @obx.ref window: EditorWindow;
private resourceTypeMap: Map<string, ResourceType> = new Map();
private resourceList: Resource[] = [];
constructor( constructor(
readonly registryInnerPlugin: (designer: Designer, editor: Editor, plugins: Plugins) => Promise<void>, readonly registryInnerPlugin: (designer: Designer, editor: Editor, plugins: Plugins) => Promise<void>,
readonly shellModelFactory: any, readonly shellModelFactory: any,
@ -66,10 +78,6 @@ export class Workspace implements IPublicApiWorkspace {
this.emitChangeActiveWindow(); this.emitChangeActiveWindow();
} }
get isActive() {
return this._isActive;
}
setActive(value: boolean) { setActive(value: boolean) {
this._isActive = value; this._isActive = value;
} }
@ -105,14 +113,6 @@ export class Workspace implements IPublicApiWorkspace {
return this.resourceTypeMap.get(resourceName)!; return this.resourceTypeMap.get(resourceName)!;
} }
get defaultResourceType(): ResourceType | null {
if (this.resourceTypeMap.size >= 1) {
return Array.from(this.resourceTypeMap.values())[0];
}
return null;
}
removeResourceType(resourceName: string) { removeResourceType(resourceName: string) {
if (this.resourceTypeMap.has(resourceName)) { if (this.resourceTypeMap.has(resourceName)) {
this.resourceTypeMap.delete(resourceName); this.resourceTypeMap.delete(resourceName);
@ -153,13 +153,17 @@ export class Workspace implements IPublicApiWorkspace {
console.error(`${name} is not available`); console.error(`${name} is not available`);
return; return;
} }
const filterWindows = this.windows.filter(d => (d.resource.name === name && d.title == title)); const filterWindows = this.windows.filter(d => (d.resource.name === name && d.resource.title == title));
if (filterWindows && filterWindows.length) { if (filterWindows && filterWindows.length) {
this.window = filterWindows[0]; this.window = filterWindows[0];
this.emitChangeActiveWindow(); this.emitChangeActiveWindow();
return; return;
} }
const resource = new Resource({}, resourceType, this); const resource = new Resource({
resourceName: name,
title,
options,
}, resourceType, this);
this.window = new EditorWindow(resource, this, title, options); this.window = new EditorWindow(resource, this, title, options);
this.windows.push(this.window); this.windows.push(this.window);
this.editorWindowMap.set(this.window.id, this.window); this.editorWindowMap.set(this.window.id, this.window);