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}`
### 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}`
相关类型:[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
@ -644,4 +644,4 @@ isConditionalVisible(): boolean | undefined;
setConditionalVisible(): void;
```
**@since v1.1.0**
**@since v1.1.0**

View File

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

View File

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

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

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

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

@ -30,7 +30,6 @@ 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 { BemToolsManager } from '../builtin-simulator/bem-tools/manager';
import { ComponentActions } from '../component-actions';
@ -241,9 +240,6 @@ export class Designer implements IDesigner {
this.postEvent('init', this);
this.setupSelection();
setupHistory();
// TODO: 先简单实现,后期通过焦点赋值
focusing.focusDesigner = this;
}
setupSelection = () => {
@ -341,6 +337,7 @@ export class Designer implements IDesigner {
/**
*
* @deprecated
*/
getSuitableInsertion(
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 './setting';
export * from './active-tracker';
export * from './focusing';
export * from '../document';
export * from './clipboard';

View File

@ -17,11 +17,11 @@ import { IProject, Project } from '../project';
import { ISimulatorHost } from '../simulator';
import { ComponentMeta } from '../component-meta';
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 { History } from './history';
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';
export type GetDataType<T, NodeType> = T extends undefined
@ -32,7 +32,7 @@ export type GetDataType<T, NodeType> = T extends undefined
: any
: T;
export interface IDocumentModel extends Omit< IPublicModelDocumentModel, 'selection' > {
export interface IDocumentModel extends Omit< IPublicModelDocumentModel, 'selection' | 'checkNesting' > {
readonly designer: Designer;
@ -59,6 +59,11 @@ export interface IDocumentModel extends Omit< IPublicModelDocumentModel, 'select
get rootNode(): INode | null;
checkNesting(
dropTarget: INode,
dragObject: IPublicTypeDragNodeObject | IPublicTypeNodeSchema | INode | IPublicTypeDragNodeDataObject,
): boolean;
}
export class DocumentModel implements IDocumentModel {
@ -569,7 +574,10 @@ export class DocumentModel implements IDocumentModel {
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>;
if (isDragNodeDataObject(dragObject)) {
items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];

View File

@ -16,7 +16,7 @@ import {
IPublicModelExclusiveGroup,
IPublicEnumTransformStage,
} 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 { Props, getConvertedExtraKey, IProps } from './props/props';
import { DocumentModel, IDocumentModel } from '../document-model';
@ -109,6 +109,8 @@ export interface INode extends IPublicModelNode {
getExtraProp(key: string, createIfNone?: boolean): IProp | null;
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);
}
@ -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;
// 如果节点是模态框,插入到根节点下
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 ||
@ -1171,7 +1173,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;
@ -1304,22 +1306,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;
@ -1340,12 +1335,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;
}
@ -1367,7 +1362,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;
}
@ -1396,11 +1391,11 @@ 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;
): INode {
let node: INode;
if (isNode(thing) && (copy || thing.isSlot())) {
thing = thing.export(IPublicEnumTransformStage.Clone);
}
@ -1410,20 +1405,20 @@ export function insertChild(
node = container.document.createNode(thing);
}
container.children.internalInsert(node, at);
container.children.insert(node, at);
return node;
}
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

@ -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;

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

View File

@ -1,18 +1,102 @@
/* eslint-disable max-len */
import { isFormEvent } from '@alilc/lowcode-utils';
import {
focusing,
insertChildren,
clipboard,
} from '@alilc/lowcode-designer';
import { isFormEvent, isNodeSchema, isNode } from '@alilc/lowcode-utils';
import {
IPublicModelPluginContext,
IPublicEnumTransformStage,
IPublicModelNode,
IPublicTypeNodeSchema,
IPublicTypeNodeData,
IPublicEnumDragObjectType,
IPublicTypeDragNodeObject,
} 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 */
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;
}
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
export const builtinHotkey = (ctx: IPublicModelPluginContext) => {
return {
init() {
const { hotkey, project, logger, canvas } = ctx;
const { clipboard } = canvas;
// hotkey binding
hotkey.bind(['backspace', 'del'], (e: KeyboardEvent, action) => {
logger.info(`action ${action} is triggered`);
@ -108,11 +254,11 @@ export const builtinHotkey = (ctx: IPublicModelPluginContext) => {
hotkey.bind('escape', (e: KeyboardEvent, action) => {
logger.info(`action ${action} is triggered`);
// const currentFocus = focusing.current;
if (canvas.isInLiveEditing) {
return;
}
const sel = focusing.focusDesigner?.currentDocument?.selection;
const sel = project.currentDocument?.selection;
if (isFormEvent(e) || !sel) {
return;
}
@ -168,26 +314,31 @@ export const builtinHotkey = (ctx: IPublicModelPluginContext) => {
return;
}
// TODO
const designer = focusing.focusDesigner;
const doc = project?.currentDocument;
if (isFormEvent(e) || !designer || !doc) {
if (isFormEvent(e) || !doc) {
return;
}
/* istanbul ignore next */
clipboard.waitPasteData(e, ({ componentsTree }) => {
if (componentsTree) {
const { target, index } = designer.getSuitableInsertion(componentsTree) || {};
const { target, index } = getSuitableInsertion(ctx, componentsTree) || {};
if (!target) {
return;
}
let canAddComponentsTree = componentsTree.filter((i) => {
return (doc as any)[documentSymbol].checkNestingUp(target, i);
let canAddComponentsTree = componentsTree.filter((node: IPublicModelNode) => {
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);
if (nodes) {
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;
if (silbing) {
if (silbing.isContainerNode) {
const place = (silbing as any)[nodeSymbol].getSuitablePlace(firstNode, null);
const place = getSuitablePlaceForNode(silbing, firstNode, null);
silbing.insertAfter(firstNode, place.ref, true);
} else {
parent.insertBefore(firstNode, silbing, true);
}
firstNode?.select();
} else {
const place = (parent as any)[nodeSymbol].getSuitablePlace(firstNode, null); // upwards
const place = getSuitablePlaceForNode(parent, firstNode, null); // upwards
if (place) {
const container = place.container.internalToShellNode();
container.insertBefore(firstNode, place.ref);
@ -381,7 +532,7 @@ export const builtinHotkey = (ctx: IPublicModelPluginContext) => {
}
firstNode?.select();
} else {
const place = (parent as any)[nodeSymbol].getSuitablePlace(firstNode, null); // upwards
const place = getSuitablePlaceForNode(parent, firstNode, null); // upwards
if (place) {
const container = place.container.internalToShellNode();
container.insertAfter(firstNode, place.ref, true);

View File

@ -11,5 +11,12 @@
"Slots": "Slots",
"Slot for {prop}": "Slot for {prop}",
"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"
}

View File

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

View File

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

View File

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

View File

@ -6,7 +6,6 @@ import { IPublicModelPluginContext } from '@alilc/lowcode-types';
import Filter from './filter';
import { TreeMaster } from '../controllers/tree-master';
export class Pane extends Component<{
config: any;
pluginContext: IPublicModelPluginContext;
@ -40,7 +39,7 @@ export class Pane extends Component<{
return (
<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">
<TreeView key={tree.id} tree={tree} pluginContext={this.props.pluginContext} />
</div>

View File

@ -38,7 +38,7 @@ class ModalTreeNodeView extends Component<{
return (
<div className="tree-node-modal">
<div className="tree-node-modal-title">
<span></span>
<span>{this.pluginContext.intlNode('Modal View')}</span>
<div
className="tree-node-modal-title-visible-icon"
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 { isRendererDetached } from './utils/misc';
import './renderer.less';
import { createIntl } from './locale';
// patch cloneElement avoid lost keyProps
const originCloneElement = window.React.cloneElement;
@ -130,6 +131,7 @@ class Renderer extends Component<{
documentInstance: DocumentInstance;
}> {
startTime: number | null = null;
schemaChangedSymbol = false;
componentDidUpdate() {
this.recordTime();
@ -152,8 +154,6 @@ class Renderer extends Component<{
this.recordTime();
}
schemaChangedSymbol = false;
getSchemaChangedSymbol = () => {
return this.schemaChangedSymbol;
};
@ -172,6 +172,8 @@ class Renderer extends Component<{
if (!container.autoRender || isRendererDetached()) return null;
const { intl } = createIntl(locale);
return (
<LowCodeRenderer
locale={locale}
@ -206,12 +208,12 @@ class Renderer extends Component<{
(children == null || (Array.isArray(children) && !children.length)) &&
(!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) => {
return node?.getExtraProp('isLocked')?.getValue() === true;
});
if (lockedNode) {
defaultPlaceholder = '锁定元素及子元素无法编辑';
defaultPlaceholder = intl('Locked elements and child elements cannot be edited');
}
children = (
<div className={cn('lc-container-placeholder', { 'lc-container-locked': !!lockedNode })} style={viewProps.placeholderStyle}>

View File

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

View File

@ -9,6 +9,7 @@ import {
Dragon,
SettingPropEntry,
SettingTopEntry,
Clipboard,
} from './model';
import {
Project,
@ -57,4 +58,5 @@ export {
Logger,
Canvas,
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,
IPublicModelDocumentModel,
IPublicTypeOnChangeOptions,
IPublicModelDragObject,
IPublicTypeDragNodeObject,
IPublicTypeDragNodeDataObject,
IPublicModelNode,
@ -227,9 +226,11 @@ export class DocumentModel implements IPublicModelDocumentModel {
dropTarget: IPublicModelNode,
dragObject: IPublicTypeDragNodeObject | IPublicTypeDragNodeDataObject,
): boolean {
let innerDragObject: IPublicModelDragObject = dragObject;
let innerDragObject = 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(
((dropTarget as any)[nodeSymbol] || dropTarget) as any,

View File

@ -17,4 +17,5 @@ export * from './setting-top-entry';
export * from './resource';
export * from './active-tracker';
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 pluginInstanceSymbol = Symbol('plugin-instance');
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';
/**
@ -54,4 +54,12 @@ export interface IPublicApiCanvas {
* @since v1.1.0
*/
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 './sensor';
export * from './resource';
export * from './clipboard';

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { Component } from 'react';
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 TopArea from './top-area';
import LeftArea from './left-area';
@ -46,9 +46,9 @@ export class Workbench extends Component<{
<div className="lc-workspace-workbench-window">
{
workspace.windows.map(d => (
<EditorWindowView
<WindowView
active={d.id === workspace.window.id}
editorWindow={d}
window={d}
key={d.id}
/>
))

View File

@ -1,6 +1,6 @@
import { IPublicTypeEditorView, IPublicModelResource, IPublicResourceData, IPublicResourceTypeConfig } from '@alilc/lowcode-types';
import { Logger } from '@alilc/lowcode-utils';
import { BasicContext } from './base-context';
import { BasicContext } from './context/base-context';
import { ResourceType } from './resource-type';
import { Workspace as InnerWorkSpace } from './workspace';
@ -13,20 +13,6 @@ export class Resource implements IPublicModelResource {
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() {
return this.resourceType.name;
}
@ -55,6 +41,24 @@ export class Resource implements IPublicModelResource {
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() {
await this.resourceTypeInstance.init?.();
await this.context.innerPlugins.init();
@ -63,6 +67,7 @@ export class Resource implements IPublicModelResource {
async import(schema: any) {
return await this.resourceTypeInstance.import?.(schema);
}
async save(value: any) {
return await this.resourceTypeInstance.save?.(value);
}

View File

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

View File

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