mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-03-05 17:57:13 +00:00
Merge branch 'develop' into release/1.0.14-beta
This commit is contained in:
commit
d7237d7502
@ -9,7 +9,7 @@ const jestConfig = {
|
||||
// // '^.+\\.(ts|tsx)$': 'ts-jest',
|
||||
// // '^.+\\.(js|jsx)$': 'babel-jest',
|
||||
// },
|
||||
// testMatch: ['**/document/node/node.test.ts'],
|
||||
// testMatch: ['**/node-children.test.ts'],
|
||||
// testMatch: ['**/history/history.test.ts'],
|
||||
// testMatch: ['**/plugin/plugin-manager.test.ts'],
|
||||
// testMatch: ['(/tests?/.*(test))\\.[jt]s$'],
|
||||
@ -31,6 +31,7 @@ const jestConfig = {
|
||||
'!src/builtin-simulator/live-editing/live-editing.ts',
|
||||
'!src/designer/offset-observer.ts',
|
||||
'!src/designer/clipboard.ts',
|
||||
'!src/designer/scroller.ts',
|
||||
'!src/builtin-simulator/host.ts',
|
||||
'!**/node_modules/**',
|
||||
'!**/vendor/**',
|
||||
|
||||
@ -2,35 +2,7 @@ import { EventEmitter } from 'events';
|
||||
import { ISimulatorHost } from '../../simulator';
|
||||
import { Designer, Point } from '../../designer';
|
||||
import { cursor } from '@alilc/lowcode-utils';
|
||||
// import Cursor from './cursor';
|
||||
// import Pages from './pages';
|
||||
|
||||
function makeEventsHandler(
|
||||
boostEvent: MouseEvent | DragEvent,
|
||||
sensors: ISimulatorHost[],
|
||||
): (fn: (sdoc: Document) => void) => void {
|
||||
const topDoc = window.document;
|
||||
const sourceDoc = boostEvent.view?.document || topDoc;
|
||||
// TODO: optimize this logic, reduce listener
|
||||
// const boostPrevented = boostEvent.defaultPrevented;
|
||||
const docs = new Set<Document>();
|
||||
// if (boostPrevented || isDragEvent(boostEvent)) {
|
||||
docs.add(topDoc);
|
||||
// }
|
||||
docs.add(sourceDoc);
|
||||
// if (sourceDoc !== topDoc || isDragEvent(boostEvent)) {
|
||||
sensors.forEach(sim => {
|
||||
const sdoc = sim.contentDocument;
|
||||
if (sdoc) {
|
||||
docs.add(sdoc);
|
||||
}
|
||||
});
|
||||
// }
|
||||
|
||||
return (handle: (sdoc: Document) => void) => {
|
||||
docs.forEach(doc => handle(doc));
|
||||
};
|
||||
}
|
||||
import { makeEventsHandler } from '../../utils/misc';
|
||||
|
||||
// 拖动缩放
|
||||
export default class DragResizeEngine {
|
||||
@ -73,6 +45,7 @@ export default class DragResizeEngine {
|
||||
|
||||
const masterSensors = this.getMasterSensors();
|
||||
|
||||
/* istanbul ignore next */
|
||||
const createResizeEvent = (e: MouseEvent | DragEvent): Point => {
|
||||
const sourceDocument = e.view?.document;
|
||||
|
||||
|
||||
@ -54,17 +54,17 @@ export function createSimulator(
|
||||
const id = asset.id ? ` data-id="${asset.id}"` : '';
|
||||
const lv = asset.level || level || AssetLevel.Environment;
|
||||
if (asset.type === AssetType.JSUrl) {
|
||||
(scripts[lv] || scripts[AssetLevel.App]).push(
|
||||
scripts[lv].push(
|
||||
`<script src="${asset.content}"${id}></script>`,
|
||||
);
|
||||
} else if (asset.type === AssetType.JSText) {
|
||||
(scripts[lv] || scripts[AssetLevel.App]).push(`<script${id}>${asset.content}</script>`);
|
||||
scripts[lv].push(`<script${id}>${asset.content}</script>`);
|
||||
} else if (asset.type === AssetType.CSSUrl) {
|
||||
(styles[lv] || styles[AssetLevel.App]).push(
|
||||
styles[lv].push(
|
||||
`<link rel="stylesheet" href="${asset.content}"${id} />`,
|
||||
);
|
||||
} else if (asset.type === AssetType.CSSText) {
|
||||
(styles[lv] || styles[AssetLevel.App]).push(
|
||||
styles[lv].push(
|
||||
`<style type="text/css"${id}>${asset.content}</style>`,
|
||||
);
|
||||
}
|
||||
@ -98,8 +98,9 @@ export function createSimulator(
|
||||
doc.close();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
if (win.SimulatorRenderer || host.renderer) {
|
||||
return resolve(win.SimulatorRenderer || host.renderer);
|
||||
const renderer = win.SimulatorRenderer || host.renderer;
|
||||
if (renderer) {
|
||||
return resolve(renderer);
|
||||
}
|
||||
const loaded = () => {
|
||||
resolve(win.SimulatorRenderer || host.renderer);
|
||||
|
||||
@ -420,7 +420,7 @@ export class Designer {
|
||||
}
|
||||
|
||||
get(key: string): any {
|
||||
return this.props ? this.props[key] : null;
|
||||
return this.props?.[key];
|
||||
}
|
||||
|
||||
@obx.ref private _simulatorComponent?: ComponentType<any>;
|
||||
|
||||
@ -6,6 +6,7 @@ import { DropLocation } from './location';
|
||||
import { Node, DocumentModel } from '../document';
|
||||
import { ISimulatorHost, isSimulatorHost, NodeInstance, ComponentInstance } from '../simulator';
|
||||
import { Designer } from './designer';
|
||||
import { makeEventsHandler } from '../utils/misc';
|
||||
|
||||
export interface LocateEvent {
|
||||
readonly type: 'LocateEvent';
|
||||
@ -135,7 +136,7 @@ export function isShaken(e1: MouseEvent | DragEvent, e2: MouseEvent | DragEvent)
|
||||
);
|
||||
}
|
||||
|
||||
function isInvalidPoint(e: any, last: any): boolean {
|
||||
export function isInvalidPoint(e: any, last: any): boolean {
|
||||
return (
|
||||
e.clientX === 0 &&
|
||||
e.clientY === 0 &&
|
||||
@ -144,7 +145,7 @@ function isInvalidPoint(e: any, last: any): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isSameAs(e1: MouseEvent | DragEvent, e2: MouseEvent | DragEvent): boolean {
|
||||
export function isSameAs(e1: MouseEvent | DragEvent, e2: MouseEvent | DragEvent): boolean {
|
||||
return e1.clientY === e2.clientY && e1.clientX === e2.clientX;
|
||||
}
|
||||
|
||||
@ -159,31 +160,6 @@ function getSourceSensor(dragObject: DragObject): ISimulatorHost | null {
|
||||
return dragObject.nodes[0]?.document.simulator || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* make a handler that listen all sensors:document, avoid frame lost
|
||||
*/
|
||||
function makeEventsHandler(
|
||||
boostEvent: MouseEvent | DragEvent,
|
||||
sensors: ISimulatorHost[],
|
||||
): (fn: (sdoc: Document) => void) => void {
|
||||
const topDoc = window.document;
|
||||
const sourceDoc = boostEvent.view?.document || topDoc;
|
||||
// TODO: optimize this logic, reduce listener
|
||||
const docs = new Set<Document>();
|
||||
docs.add(topDoc);
|
||||
docs.add(sourceDoc);
|
||||
sensors.forEach((sim) => {
|
||||
const sdoc = sim.contentDocument;
|
||||
if (sdoc) {
|
||||
docs.add(sdoc);
|
||||
}
|
||||
});
|
||||
|
||||
return (handle: (sdoc: Document) => void) => {
|
||||
docs.forEach((doc) => handle(doc));
|
||||
};
|
||||
}
|
||||
|
||||
function isDragEvent(e: any): e is DragEvent {
|
||||
return e?.type?.startsWith('drag');
|
||||
}
|
||||
@ -325,6 +301,7 @@ export class Dragon {
|
||||
const locateEvent = createLocateEvent(e);
|
||||
const sensor = chooseSensor(locateEvent);
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (isRGL) {
|
||||
// 禁止被拖拽元素的阻断
|
||||
const nodeInst = dragObject.nodes[0].getDOMNode();
|
||||
@ -429,6 +406,7 @@ export class Dragon {
|
||||
// 发送drop事件
|
||||
if (e) {
|
||||
const { isRGL, rglNode } = getRGL(e);
|
||||
/* istanbul ignore next */
|
||||
if (isRGL && this._canDrop) {
|
||||
const tarNode = dragObject.nodes[0];
|
||||
if (rglNode.id !== tarNode.id) {
|
||||
@ -468,7 +446,7 @@ export class Dragon {
|
||||
this._dragging = false;
|
||||
try {
|
||||
this.emitter.emit('dragend', { dragObject, copy });
|
||||
} catch (ex) {
|
||||
} catch (ex) /* istanbul ignore next */ {
|
||||
exception = ex;
|
||||
}
|
||||
}
|
||||
@ -489,6 +467,7 @@ export class Dragon {
|
||||
doc.removeEventListener('keydown', checkcopy, false);
|
||||
doc.removeEventListener('keyup', checkcopy, false);
|
||||
});
|
||||
/* istanbul ignore next */
|
||||
if (exception) {
|
||||
throw exception;
|
||||
}
|
||||
@ -509,7 +488,7 @@ export class Dragon {
|
||||
if (!sourceDocument || sourceDocument === document) {
|
||||
evt.globalX = e.clientX;
|
||||
evt.globalY = e.clientY;
|
||||
} /* istanbul ignore next */ else {
|
||||
} else /* istanbul ignore next */ {
|
||||
// event from simulator sandbox
|
||||
let srcSim: ISimulatorHost | undefined;
|
||||
const lastSim = lastSensor && isSimulatorHost(lastSensor) ? lastSensor : null;
|
||||
@ -616,6 +595,7 @@ export class Dragon {
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
private getMasterSensors(): ISimulatorHost[] {
|
||||
return Array.from(
|
||||
new Set(
|
||||
|
||||
@ -220,8 +220,10 @@ export class DocumentModel {
|
||||
if (this.hasNode(schema?.id)) {
|
||||
schema.id = null;
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
if (schema.id) {
|
||||
node = this.getNode(schema.id);
|
||||
// TODO: 底下这几段代码似乎永远都进不去
|
||||
if (node && node.componentName === schema.componentName) {
|
||||
if (node.parent) {
|
||||
node.internalSetParent(null, false);
|
||||
@ -239,12 +241,6 @@ export class DocumentModel {
|
||||
// todo: this.activeNodes?.push(node);
|
||||
}
|
||||
|
||||
const origin = this._nodesMap.get(node.id);
|
||||
if (origin && origin !== node) {
|
||||
// almost will not go here, ensure the id is unique
|
||||
origin.internalSetWillPurge();
|
||||
}
|
||||
|
||||
this._nodesMap.set(node.id, node);
|
||||
this.nodes.add(node);
|
||||
|
||||
@ -578,6 +574,7 @@ export class DocumentModel {
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
getAddonData(name: string) {
|
||||
const addon = this._addons.find((item) => item.name === name);
|
||||
if (addon) {
|
||||
@ -588,6 +585,7 @@ export class DocumentModel {
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
exportAddonData() {
|
||||
const addons = {};
|
||||
this._addons.forEach((addon) => {
|
||||
@ -604,6 +602,7 @@ export class DocumentModel {
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
/* istanbul ignore next */
|
||||
registerAddon(name: string, exportData: any) {
|
||||
if (['id', 'params', 'layout'].indexOf(name) > -1) {
|
||||
throw new Error('addon name cannot be id, params, layout');
|
||||
@ -618,6 +617,7 @@ export class DocumentModel {
|
||||
});
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
acceptRootNodeVisitor(
|
||||
visitorName = 'default',
|
||||
visitorFn: (node: RootNode) => any,
|
||||
@ -637,6 +637,7 @@ export class DocumentModel {
|
||||
return visitorResult;
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
getRootNodeVisitor(name: string) {
|
||||
return this.rootNodeVisitorMap[name];
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { EventEmitter } from 'events';
|
||||
import { Node } from './node';
|
||||
import { DocumentModel } from '../document-model';
|
||||
|
||||
function getModalNodes(node: Node) {
|
||||
export function getModalNodes(node: Node) {
|
||||
if (!node) return [];
|
||||
let nodes: any = [];
|
||||
if (node.componentMeta.isModal) {
|
||||
@ -40,44 +40,37 @@ export class ModalNodesManager {
|
||||
];
|
||||
}
|
||||
|
||||
public getModalNodes() {
|
||||
getModalNodes() {
|
||||
return this.modalNodes;
|
||||
}
|
||||
|
||||
public getVisibleModalNode() {
|
||||
const visibleNode = this.modalNodes
|
||||
? this.modalNodes.find((node: Node) => {
|
||||
return node.getVisible();
|
||||
})
|
||||
: null;
|
||||
return visibleNode;
|
||||
getVisibleModalNode() {
|
||||
return this.getModalNodes().find((node: Node) => node.getVisible());
|
||||
}
|
||||
|
||||
public hideModalNodes() {
|
||||
if (this.modalNodes) {
|
||||
this.modalNodes.forEach((node: Node) => {
|
||||
node.setVisible(false);
|
||||
});
|
||||
}
|
||||
hideModalNodes() {
|
||||
this.modalNodes.forEach((node: Node) => {
|
||||
node.setVisible(false);
|
||||
});
|
||||
}
|
||||
|
||||
public setVisible(node: Node) {
|
||||
setVisible(node: Node) {
|
||||
this.hideModalNodes();
|
||||
node.setVisible(true);
|
||||
}
|
||||
|
||||
public setInvisible(node: Node) {
|
||||
setInvisible(node: Node) {
|
||||
node.setVisible(false);
|
||||
}
|
||||
|
||||
public onVisibleChange(func: () => any) {
|
||||
onVisibleChange(func: () => any) {
|
||||
this.emitter.on('visibleChange', func);
|
||||
return () => {
|
||||
this.emitter.removeListener('visibleChange', func);
|
||||
};
|
||||
}
|
||||
|
||||
public onModalNodesChange(func: () => any) {
|
||||
onModalNodesChange(func: () => any) {
|
||||
this.emitter.on('modalNodesChange', func);
|
||||
return () => {
|
||||
this.emitter.removeListener('modalNodesChange', func);
|
||||
@ -122,7 +115,7 @@ export class ModalNodesManager {
|
||||
}
|
||||
}
|
||||
|
||||
public setNodes() {
|
||||
setNodes() {
|
||||
const nodes = getModalNodes(this.page.getRoot()!);
|
||||
this.modalNodes = nodes;
|
||||
this.modalNodes.forEach((node: Node) => {
|
||||
|
||||
@ -161,6 +161,7 @@ export class NodeChildren {
|
||||
}
|
||||
}
|
||||
const { document } = node;
|
||||
/* istanbul ignore next */
|
||||
if (globalContext.has('editor')) {
|
||||
globalContext.get('editor').emit('node.remove', { node, index: i });
|
||||
}
|
||||
@ -197,6 +198,7 @@ export class NodeChildren {
|
||||
const i = children.indexOf(node);
|
||||
|
||||
if (node.parent) {
|
||||
/* istanbul ignore next */
|
||||
globalContext.has('editor') &&
|
||||
globalContext.get('editor').emit('node.remove.topLevel', {
|
||||
node,
|
||||
@ -229,6 +231,7 @@ export class NodeChildren {
|
||||
node,
|
||||
});
|
||||
this.emitter.emit('insert', node);
|
||||
/* istanbul ignore next */
|
||||
if (globalContext.has('editor')) {
|
||||
globalContext.get('editor').emit('node.add', { node });
|
||||
}
|
||||
|
||||
@ -259,7 +259,7 @@ export class Prop implements IPropParent {
|
||||
} else {
|
||||
this._type = 'map';
|
||||
}
|
||||
} /* istanbul ignore next */ else {
|
||||
} else /* istanbul ignore next */ {
|
||||
this._type = 'expression';
|
||||
this._value = {
|
||||
type: 'JSExpression',
|
||||
@ -502,6 +502,7 @@ export class Prop implements IPropParent {
|
||||
*/
|
||||
@action
|
||||
delete(prop: Prop): void {
|
||||
/* istanbul ignore else */
|
||||
if (this._items) {
|
||||
const i = this._items.indexOf(prop);
|
||||
if (i > -1) {
|
||||
@ -519,6 +520,7 @@ export class Prop implements IPropParent {
|
||||
*/
|
||||
@action
|
||||
deleteKey(key: string): void {
|
||||
/* istanbul ignore else */
|
||||
if (this.maps) {
|
||||
const prop = this.maps.get(key);
|
||||
if (prop) {
|
||||
|
||||
@ -147,9 +147,8 @@ export class Selection {
|
||||
if (n === PositionNO.Contains || n === PositionNO.TheSame) {
|
||||
isTop = false;
|
||||
break;
|
||||
}
|
||||
// node contains nodes[i], delete nodes[i]
|
||||
if (n === PositionNO.ContainedBy) {
|
||||
} else if (n === PositionNO.ContainedBy) {
|
||||
// node contains nodes[i], delete nodes[i]
|
||||
nodes.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import Viewport from '../builtin-simulator/viewport';
|
||||
import { ISimulatorHost } from '../simulator';
|
||||
|
||||
export function isElementNode(domNode: Element) {
|
||||
return domNode.nodeType === Node.ELEMENT_NODE;
|
||||
@ -28,4 +29,28 @@ export function isDOMNodeVisible(domNode: Element, viewport: Viewport) {
|
||||
*/
|
||||
export function normalizeTriggers(triggers: string[]) {
|
||||
return triggers.map((trigger: string) => trigger?.toUpperCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* make a handler that listen all sensors:document, avoid frame lost
|
||||
*/
|
||||
export function makeEventsHandler(
|
||||
boostEvent: MouseEvent | DragEvent,
|
||||
sensors: ISimulatorHost[],
|
||||
): (fn: (sdoc: Document) => void) => void {
|
||||
const topDoc = window.document;
|
||||
const sourceDoc = boostEvent.view?.document || topDoc;
|
||||
const docs = new Set<Document>();
|
||||
docs.add(topDoc);
|
||||
docs.add(sourceDoc);
|
||||
sensors.forEach((sim) => {
|
||||
const sdoc = sim.contentDocument;
|
||||
if (sdoc) {
|
||||
docs.add(sdoc);
|
||||
}
|
||||
});
|
||||
|
||||
return (handle: (sdoc: Document) => void) => {
|
||||
docs.forEach((doc) => handle(doc));
|
||||
};
|
||||
}
|
||||
@ -57,7 +57,8 @@ describe('DragResizeEngine 测试', () => {
|
||||
});
|
||||
|
||||
// do nothing
|
||||
resizeEngine.from();
|
||||
const noop = resizeEngine.from();
|
||||
noop();
|
||||
|
||||
const offFrom = resizeEngine.from(document, 'e', mockedBoostFn);
|
||||
|
||||
|
||||
@ -273,6 +273,18 @@ describe('Designer 测试', () => {
|
||||
expect(designer._componentMetasMap.has('Div')).toBeTruthy();
|
||||
const { editor: editorFromDesigner2, ...others2 } = designer.props;
|
||||
expect(others2).toEqual(updatedProps);
|
||||
|
||||
// 第三次设置 props,跟第二次值一样,for 覆盖率测试
|
||||
const updatedProps2 = updatedProps;
|
||||
designer.setProps(updatedProps2);
|
||||
|
||||
expect(designer.simulatorComponent).toEqual({ isSimulatorComp2: true });
|
||||
expect(designer.simulatorProps).toEqual({ designMode: 'live' });
|
||||
expect(designer.suspensed).toBeFalsy();
|
||||
expect(designer._componentMetasMap.has('Button')).toBeTruthy();
|
||||
expect(designer._componentMetasMap.has('Div')).toBeTruthy();
|
||||
const { editor: editorFromDesigner3, ...others3 } = designer.props;
|
||||
expect(others3).toEqual(updatedProps);
|
||||
});
|
||||
|
||||
describe('getSuitableInsertion', () => {
|
||||
@ -313,6 +325,70 @@ describe('Designer 测试', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('getComponentMetasMap', () => {
|
||||
designer.createComponentMeta({
|
||||
componentName: 'Div',
|
||||
title: '容器',
|
||||
docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
|
||||
devMode: 'procode',
|
||||
tags: ['布局'],
|
||||
});
|
||||
|
||||
expect(designer.getComponentMetasMap().get('Div')).not.toBeUndefined();
|
||||
});
|
||||
|
||||
it('refreshComponentMetasMap', () => {
|
||||
designer.createComponentMeta({
|
||||
componentName: 'Div',
|
||||
title: '容器',
|
||||
docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
|
||||
devMode: 'procode',
|
||||
tags: ['布局'],
|
||||
});
|
||||
|
||||
const originalMetasMap = designer.getComponentMetasMap();
|
||||
designer.refreshComponentMetasMap();
|
||||
|
||||
expect(originalMetasMap).not.toBe(designer.getComponentMetasMap());
|
||||
});
|
||||
|
||||
describe('loadIncrementalAssets', () => {
|
||||
it('components && packages', async () => {
|
||||
editor.set('assets', { components: [], packages: [] });
|
||||
const fn = jest.fn();
|
||||
|
||||
project.mountSimulator({
|
||||
setupComponents: fn,
|
||||
});
|
||||
await designer.loadIncrementalAssets({
|
||||
components: [{
|
||||
componentName: 'Div2',
|
||||
title: '容器',
|
||||
docUrl: 'http://gitlab.alibaba-inc.com/vision-components/vc-block/blob/master/README.md',
|
||||
devMode: 'proCode',
|
||||
tags: ['布局'],
|
||||
}],
|
||||
packages: [],
|
||||
});
|
||||
|
||||
const comps = editor.get('assets').components;
|
||||
expect(comps).toHaveLength(1);
|
||||
expect(fn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('no components && packages', async () => {
|
||||
editor.set('assets', { components: [], packages: [] });
|
||||
const fn = jest.fn();
|
||||
|
||||
project.mountSimulator({
|
||||
setupComponents: fn,
|
||||
});
|
||||
await designer.loadIncrementalAssets({});
|
||||
|
||||
expect(fn).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('createLocation / clearLocation', () => {
|
||||
const mockTarget = {
|
||||
document: doc,
|
||||
|
||||
@ -1,14 +1,18 @@
|
||||
import { Detecting } from '../../src/designer/detecting';
|
||||
|
||||
it('Detecting 测试', () => {
|
||||
const fn = jest.fn();
|
||||
const detecting = new Detecting();
|
||||
detecting.onDetectingChange(fn);
|
||||
|
||||
expect(detecting.enable).toBeTruthy();
|
||||
|
||||
const mockNode = { document };
|
||||
detecting.capture(mockNode);
|
||||
expect(fn).toHaveBeenCalledWith(detecting.current);
|
||||
expect(detecting.current).toBe(mockNode);
|
||||
|
||||
detecting.release({});
|
||||
detecting.release(mockNode);
|
||||
expect(detecting.current).toBeNull();
|
||||
|
||||
|
||||
@ -3,16 +3,6 @@ import { set } from '../utils';
|
||||
import { Editor, globalContext } from '@alilc/lowcode-editor-core';
|
||||
import { Project } from '../../src/project/project';
|
||||
import { DocumentModel } from '../../src/document/document-model';
|
||||
import {
|
||||
isRootNode,
|
||||
Node,
|
||||
isNode,
|
||||
comparePosition,
|
||||
contains,
|
||||
insertChild,
|
||||
insertChildren,
|
||||
PositionNO,
|
||||
} from '../../src/document/node/node';
|
||||
import { Designer } from '../../src/designer/designer';
|
||||
import {
|
||||
Dragon,
|
||||
@ -23,12 +13,10 @@ import {
|
||||
DragObjectType,
|
||||
isShaken,
|
||||
setShaken,
|
||||
isInvalidPoint,
|
||||
isSameAs,
|
||||
} from '../../src/designer/dragon';
|
||||
import formSchema from '../fixtures/schema/form';
|
||||
import divMetadata from '../fixtures/component-metadata/div';
|
||||
import formMetadata from '../fixtures/component-metadata/form';
|
||||
import otherMeta from '../fixtures/component-metadata/other';
|
||||
import pageMetadata from '../fixtures/component-metadata/page';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
|
||||
describe('Dragon 测试', () => {
|
||||
@ -273,9 +261,32 @@ describe('Dragon 测试', () => {
|
||||
});
|
||||
|
||||
it('addSensor / removeSensor', () => {
|
||||
const sensor = {};
|
||||
const sensor = {
|
||||
locate: () => {},
|
||||
sensorAvailable: true,
|
||||
isEnter: () => true,
|
||||
fixEvent: () => {},
|
||||
deactiveSensor: () => {},
|
||||
};
|
||||
const sensor2 = {};
|
||||
dragon.addSensor(sensor);
|
||||
expect(dragon.sensors.length).toBe(1);
|
||||
expect(dragon.activeSensor).toBeUndefined();
|
||||
dragon.boost(
|
||||
{
|
||||
type: DragObjectType.NodeData,
|
||||
data: [{ componentName: 'Button' }],
|
||||
},
|
||||
new MouseEvent('mousedown', { clientX: 100, clientY: 100 }),
|
||||
);
|
||||
|
||||
fireEvent.mouseMove(document, { clientX: 108, clientY: 108 });
|
||||
fireEvent.mouseMove(document, { clientX: 110, clientY: 110 });
|
||||
fireEvent.mouseUp(document, { clientX: 118, clientY: 118 });
|
||||
expect(dragon.activeSensor).toBe(sensor);
|
||||
// remove a non-existing sensor
|
||||
dragon.removeSensor(sensor2);
|
||||
expect(dragon.sensors.length).toBe(1);
|
||||
dragon.removeSensor(sensor);
|
||||
expect(dragon.sensors.length).toBe(0);
|
||||
});
|
||||
@ -343,4 +354,16 @@ describe('导出的其他函数', () => {
|
||||
setShaken(e);
|
||||
expect(isShaken(e)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('isInvalidPoint', () => {
|
||||
expect(isInvalidPoint({ clientX: 0, clientY: 0 }, { clientX: 6, clientY: 1 })).toBeTruthy();
|
||||
expect(isInvalidPoint({ clientX: 0, clientY: 0 }, { clientX: 1, clientY: 6 })).toBeTruthy();
|
||||
expect(isInvalidPoint({ clientX: 0, clientY: 0 }, { clientX: 6, clientY: 6 })).toBeTruthy();
|
||||
expect(isInvalidPoint({ clientX: 1, clientY: 1 }, { clientX: 2, clientY: 1 })).toBeFalsy();
|
||||
});
|
||||
|
||||
it('isSameAs', () => {
|
||||
expect(isSameAs({ clientX: 1, clientY: 1 }, { clientX: 1, clientY: 1 })).toBeTruthy();
|
||||
expect(isSameAs({ clientX: 1, clientY: 1 }, { clientX: 2, clientY: 1 })).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@ -23,7 +23,7 @@ describe('document-model 测试', () => {
|
||||
project = designer.project;
|
||||
});
|
||||
|
||||
test('empty schema', () => {
|
||||
it('empty schema', () => {
|
||||
const doc = new DocumentModel(project);
|
||||
expect(doc.rootNode.id).toBe('root');
|
||||
expect(doc.currentRoot).toBe(doc.rootNode);
|
||||
@ -44,7 +44,7 @@ describe('document-model 测试', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('各种方法测试', () => {
|
||||
it('各种方法测试', () => {
|
||||
const doc = new DocumentModel(project, formSchema);
|
||||
const mockNode = { id: 1 };
|
||||
doc.addWillPurge(mockNode);
|
||||
@ -115,8 +115,89 @@ describe('document-model 测试', () => {
|
||||
expect(doc.history).toBe(doc.getHistory());
|
||||
});
|
||||
|
||||
it('focusNode - using drillDown', () => {
|
||||
const doc = new DocumentModel(project, formSchema);
|
||||
expect(doc.focusNode.id).toBe('page');
|
||||
|
||||
doc.drillDown(doc.getNode('node_k1ow3cbb'));
|
||||
expect(doc.focusNode.id).toBe('node_k1ow3cbb');
|
||||
});
|
||||
|
||||
it('focusNode - using drillDown & import', () => {
|
||||
const doc = new DocumentModel(project, formSchema);
|
||||
expect(doc.focusNode.id).toBe('page');
|
||||
|
||||
doc.drillDown(doc.getNode('node_k1ow3cbb'));
|
||||
doc.import(formSchema);
|
||||
expect(doc.focusNode.id).toBe('node_k1ow3cbb');
|
||||
});
|
||||
|
||||
it('focusNode - using focusNodeSelector', () => {
|
||||
const doc = new DocumentModel(project, formSchema);
|
||||
editor.set('focusNodeSelector', (rootNode) => {
|
||||
return rootNode.children.get(1);
|
||||
});
|
||||
expect(doc.focusNode.id).toBe('node_k1ow3cbb');
|
||||
});
|
||||
|
||||
it('getNodeCount', () => {
|
||||
const doc = new DocumentModel(project);
|
||||
// using default schema, only one node
|
||||
expect(doc.getNodeCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('getNodeSchema', () => {
|
||||
const doc = new DocumentModel(project, formSchema);
|
||||
expect(doc.getNodeSchema('page').id).toBe('page');
|
||||
});
|
||||
|
||||
it('export - with __isTopFixed__', () => {
|
||||
formSchema.children[1].props.__isTopFixed__ = true;
|
||||
const doc = new DocumentModel(project, formSchema);
|
||||
|
||||
const schema = doc.export();
|
||||
expect(schema.children).toHaveLength(3);
|
||||
expect(schema.children[0].componentName).toBe('RootContent');
|
||||
expect(schema.children[1].componentName).toBe('RootHeader');
|
||||
expect(schema.children[2].componentName).toBe('RootFooter');
|
||||
});
|
||||
|
||||
describe('createNode', () => {
|
||||
it('same id && componentName', () => {
|
||||
const doc = new DocumentModel(project, formSchema);
|
||||
const node = doc.createNode({
|
||||
componentName: 'RootFooter',
|
||||
id: 'node_k1ow3cbc',
|
||||
props: {},
|
||||
condition: true,
|
||||
});
|
||||
expect(node.parent).toBeNull();
|
||||
});
|
||||
|
||||
it('same id && different componentName', () => {
|
||||
const doc = new DocumentModel(project, formSchema);
|
||||
const originalNode = doc.getNode('node_k1ow3cbc');
|
||||
const node = doc.createNode({
|
||||
componentName: 'RootFooter2',
|
||||
id: 'node_k1ow3cbc',
|
||||
props: {},
|
||||
condition: true,
|
||||
});
|
||||
// expect(originalNode.parent).toBeNull();
|
||||
expect(node.id).not.toBe('node_k1ow3cbc');
|
||||
});
|
||||
});
|
||||
|
||||
it('setSuspense', () => {
|
||||
const doc = new DocumentModel(project, formSchema);
|
||||
expect(doc.opened).toBeFalsy();
|
||||
doc.setSuspense(false);
|
||||
});
|
||||
|
||||
it('registerAddon / getAddonData / exportAddonData', () => {
|
||||
const doc = new DocumentModel(project);
|
||||
expect(doc.getAddonData('a')).toBeUndefined();
|
||||
|
||||
doc.registerAddon('a', () => 'addon a');
|
||||
doc.registerAddon('a', () => 'modified addon a');
|
||||
doc.registerAddon('b', () => 'addon b');
|
||||
@ -177,6 +258,8 @@ describe('document-model 测试', () => {
|
||||
expect(comps.find(comp => comp.componentName === 'Page')).toEqual(
|
||||
{ componentName: 'Page', devMode: 'lowCode' }
|
||||
);
|
||||
|
||||
const comps2 = doc.getComponentsMap(['Div']);
|
||||
});
|
||||
|
||||
it('acceptRootNodeVisitor / getRootNodeVisitor', () => {
|
||||
|
||||
@ -1,53 +1,35 @@
|
||||
import '../../fixtures/window';
|
||||
import { set, delayObxTick, delay } from '../../utils';
|
||||
import { Editor } from '@alilc/lowcode-editor-core';
|
||||
import { Project } from '../../../src/project/project';
|
||||
import { DocumentModel } from '../../../src/document/document-model';
|
||||
import {
|
||||
isRootNode,
|
||||
Node,
|
||||
isNode,
|
||||
comparePosition,
|
||||
contains,
|
||||
insertChild,
|
||||
insertChildren,
|
||||
PositionNO,
|
||||
} from '../../../src/document/node/node';
|
||||
import { Node } from '../../../src/document/node/node';
|
||||
import { Designer } from '../../../src/designer/designer';
|
||||
import formSchema from '../../fixtures/schema/form-with-modal';
|
||||
import divMetadata from '../../fixtures/component-metadata/div';
|
||||
import dlgMetadata from '../../fixtures/component-metadata/dialog';
|
||||
import buttonMetadata from '../../fixtures/component-metadata/button';
|
||||
import formMetadata from '../../fixtures/component-metadata/form';
|
||||
import otherMeta from '../../fixtures/component-metadata/other';
|
||||
import pageMetadata from '../../fixtures/component-metadata/page';
|
||||
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 { getModalNodes } from '../../../src/document/node/modal-nodes-manager';
|
||||
|
||||
let editor: Editor;
|
||||
let designer: Designer;
|
||||
let project: Project;
|
||||
let doc: DocumentModel;
|
||||
|
||||
beforeEach(() => {
|
||||
editor = new Editor();
|
||||
designer = new Designer({ editor });
|
||||
designer.createComponentMeta(dlgMetadata);
|
||||
project = designer.project;
|
||||
doc = new DocumentModel(project, formSchema);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
project.unload();
|
||||
designer.purge();
|
||||
editor = null;
|
||||
designer = null;
|
||||
project = null;
|
||||
});
|
||||
|
||||
describe('ModalNodesManager 方法测试', () => {
|
||||
let editor: Editor;
|
||||
let designer: Designer;
|
||||
let project: Project;
|
||||
let doc: DocumentModel;
|
||||
|
||||
beforeEach(() => {
|
||||
editor = new Editor();
|
||||
designer = new Designer({ editor });
|
||||
designer.createComponentMeta(dlgMetadata);
|
||||
project = designer.project;
|
||||
doc = new DocumentModel(project, formSchema);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
project.unload();
|
||||
designer.purge();
|
||||
editor = null;
|
||||
designer = null;
|
||||
project = null;
|
||||
});
|
||||
|
||||
it('getModalNodes / getVisibleModalNode', () => {
|
||||
const mgr = doc.modalNodesManager;
|
||||
const nodes = mgr.getModalNodes();
|
||||
@ -100,5 +82,30 @@ describe('ModalNodesManager 方法测试', () => {
|
||||
mgr.addNode(newNode);
|
||||
expect(visibleMockFn).not.toHaveBeenCalled();
|
||||
expect(nodesMockFn).not.toHaveBeenCalled();
|
||||
|
||||
const newNode2 = new Node(doc, { componentName: 'Dialog' });
|
||||
mgr.addNode(newNode2);
|
||||
mgr.setInvisible(newNode2);
|
||||
mgr.removeNode(newNode2);
|
||||
|
||||
const newNode3 = new Node(doc, { componentName: 'Dialog' });
|
||||
mgr.removeNode(newNode3);
|
||||
|
||||
const newNode4 = new Node(doc, { componentName: 'Non-Modal' });
|
||||
mgr.removeNode(newNode4);
|
||||
|
||||
const newNode5 = doc.createNode({ componentName: 'Non-Modal' });
|
||||
newNode5.remove(); // trigger node destroy
|
||||
});
|
||||
});
|
||||
|
||||
describe('其他方法', () => {
|
||||
it('getModalNodes - null', () => {
|
||||
expect(getModalNodes()).toEqual([]);
|
||||
});
|
||||
|
||||
it('getModalNodes - no children', () => {
|
||||
const node = doc.createNode({ componentName: 'Leaf', children: 'haha' });
|
||||
expect(getModalNodes(node)).toEqual([]);
|
||||
});
|
||||
});
|
||||
@ -1,29 +1,13 @@
|
||||
import '../../fixtures/window';
|
||||
import { set, delayObxTick, delay } from '../../utils';
|
||||
import { Editor } from '@alilc/lowcode-editor-core';
|
||||
import { Project } from '../../../src/project/project';
|
||||
import { DocumentModel } from '../../../src/document/document-model';
|
||||
import {
|
||||
isRootNode,
|
||||
Node,
|
||||
isNode,
|
||||
comparePosition,
|
||||
contains,
|
||||
insertChild,
|
||||
insertChildren,
|
||||
PositionNO,
|
||||
} from '../../../src/document/node/node';
|
||||
import { Designer } from '../../../src/designer/designer';
|
||||
import formSchema from '../../fixtures/schema/form';
|
||||
import divMetadata from '../../fixtures/component-metadata/div';
|
||||
import buttonMetadata from '../../fixtures/component-metadata/button';
|
||||
import formMetadata from '../../fixtures/component-metadata/form';
|
||||
import otherMeta from '../../fixtures/component-metadata/other';
|
||||
import pageMetadata from '../../fixtures/component-metadata/page';
|
||||
import rootHeaderMetadata from '../../fixtures/component-metadata/root-header';
|
||||
import rootContentMetadata from '../../fixtures/component-metadata/root-content';
|
||||
import rootFooterMetadata from '../../fixtures/component-metadata/root-footer';
|
||||
|
||||
|
||||
describe('NodeChildren 方法测试', () => {
|
||||
let editor: Editor;
|
||||
@ -57,6 +41,49 @@ describe('NodeChildren 方法测试', () => {
|
||||
expect(firstBtn.children.isEmpty()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('export', () => {
|
||||
const firstBtn = doc.getNode('node_k1ow3cbn')!;
|
||||
const { children } = firstBtn.parent!;
|
||||
|
||||
expect(children.export().length).toBe(2);
|
||||
});
|
||||
|
||||
it('export - Leaf', () => {
|
||||
const firstBtn = doc.getNode('node_k1ow3cbn')!;
|
||||
firstBtn.parent!.insertAfter({ componentName: 'Leaf', children: 'haha' });
|
||||
const { children } = firstBtn.parent!;
|
||||
|
||||
expect(children.export().length).toBe(3);
|
||||
expect(children.export()[2]).toBe('haha');
|
||||
});
|
||||
|
||||
it('import', () => {
|
||||
const firstBtn = doc.getNode('node_k1ow3cbn')!;
|
||||
const { children } = firstBtn.parent!;
|
||||
|
||||
children.import(children.export());
|
||||
|
||||
expect(children.export().length).toBe(2);
|
||||
});
|
||||
|
||||
it('delete', () => {
|
||||
const firstBtn = doc.getNode('node_k1ow3cbn')!;
|
||||
const leafNode = doc.createNode({ componentName: 'Leaf', children: 'haha' });
|
||||
firstBtn.parent!.insertAfter(leafNode);
|
||||
const { children } = firstBtn.parent!;
|
||||
|
||||
children.delete(leafNode);
|
||||
expect(children.export().length).toBe(2);
|
||||
});
|
||||
|
||||
it('delete - 插入已有的节点', () => {
|
||||
const firstBtn = doc.getNode('node_k1ow3cbn')!;
|
||||
firstBtn.parent!.insertBefore(firstBtn, firstBtn);
|
||||
const { children } = firstBtn.parent!;
|
||||
|
||||
expect(children.export().length).toBe(2);
|
||||
});
|
||||
|
||||
it('purge / for of', () => {
|
||||
const firstBtn = doc.getNode('node_k1ow3cbn')!;
|
||||
const { children } = firstBtn.parent!;
|
||||
@ -65,6 +92,9 @@ describe('NodeChildren 方法测试', () => {
|
||||
for (const child of children) {
|
||||
expect(child.isPurged).toBeTruthy();
|
||||
}
|
||||
|
||||
// purge when children is purged
|
||||
children.purge();
|
||||
});
|
||||
|
||||
it('splice', () => {
|
||||
@ -138,6 +168,28 @@ describe('NodeChildren 方法测试', () => {
|
||||
expect(found?.componentName).toBe('Button');
|
||||
});
|
||||
|
||||
it('concat', () => {
|
||||
const firstBtn = doc.getNode('node_k1ow3cbn')!;
|
||||
const { children } = firstBtn.parent!;
|
||||
|
||||
const ret = children.concat([doc.createNode({ componentName: 'Button' })]);
|
||||
|
||||
expect(ret.length).toBe(3);
|
||||
});
|
||||
|
||||
it('reduce', () => {
|
||||
const firstBtn = doc.getNode('node_k1ow3cbn')!;
|
||||
const { children } = firstBtn.parent!;
|
||||
|
||||
let ret = 0;
|
||||
ret = children.reduce((count, node) => {
|
||||
count = count + 1;
|
||||
return count;
|
||||
}, 0);
|
||||
|
||||
expect(ret).toBe(2);
|
||||
});
|
||||
|
||||
it('mergeChildren', () => {
|
||||
const firstBtn = doc.getNode('node_k1ow3cbn')!;
|
||||
const { children } = firstBtn.parent!;
|
||||
@ -159,6 +211,9 @@ describe('NodeChildren 方法测试', () => {
|
||||
expect(children.size).toBe(3);
|
||||
expect(changeMockFn).toHaveBeenCalled();
|
||||
offChange();
|
||||
|
||||
// no remover && adder && sorter
|
||||
children.mergeChildren();
|
||||
});
|
||||
|
||||
it('insert / onInsert', () => {
|
||||
|
||||
@ -95,6 +95,7 @@ describe('Prop 类测试', () => {
|
||||
it('getValue / getAsString / setValue', () => {
|
||||
expect(strProp.getValue()).toBe('haha');
|
||||
strProp.setValue('heihei');
|
||||
strProp.setValue('heihei');
|
||||
expect(strProp.getValue()).toBe('heihei');
|
||||
expect(strProp.getAsString()).toBe('heihei');
|
||||
|
||||
@ -177,6 +178,7 @@ describe('Prop 类测试', () => {
|
||||
|
||||
it('compare', () => {
|
||||
const newProp = new Prop(mockedPropsInst, 'haha');
|
||||
const newProp2 = new Prop(mockedPropsInst, { a: 1 });
|
||||
expect(strProp.compare(newProp)).toBe(0);
|
||||
expect(strProp.compare(expProp)).toBe(2);
|
||||
|
||||
@ -184,6 +186,7 @@ describe('Prop 类测试', () => {
|
||||
expect(strProp.compare(newProp)).toBe(2);
|
||||
strProp.unset();
|
||||
expect(strProp.compare(newProp)).toBe(0);
|
||||
expect(strProp.compare(newProp2)).toBe(2);
|
||||
});
|
||||
|
||||
it('isVirtual', () => {
|
||||
@ -435,6 +438,28 @@ describe('Prop 类测试', () => {
|
||||
prop = new Prop(mockedPropsInst, [undefined, undefined], '___loopArgs___');
|
||||
expect(prop.getValue()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('迭代器 / map / forEach', () => {
|
||||
const listProp = new Prop(mockedPropsInst, [1, 2]);
|
||||
const mockedFn = jest.fn();
|
||||
for (const item of listProp) {
|
||||
mockedFn();
|
||||
}
|
||||
expect(mockedFn).toHaveBeenCalledTimes(2);
|
||||
mockedFn.mockClear();
|
||||
|
||||
listProp.forEach((item) => {
|
||||
mockedFn();
|
||||
});
|
||||
expect(mockedFn).toHaveBeenCalledTimes(2);
|
||||
mockedFn.mockClear();
|
||||
|
||||
listProp.map((item) => {
|
||||
return mockedFn();
|
||||
});
|
||||
expect(mockedFn).toHaveBeenCalledTimes(2);
|
||||
mockedFn.mockClear();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -67,6 +67,7 @@ describe('选择区测试', () => {
|
||||
expect(selection.selected).toEqual(['node_k1ow3cbj', 'form']);
|
||||
selectionChangeHandler.mockClear();
|
||||
|
||||
selection.remove('node_k1ow3cbj_fake');
|
||||
selection.remove('node_k1ow3cbj');
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form']);
|
||||
@ -141,7 +142,7 @@ describe('选择区测试', () => {
|
||||
selectionChangeHandler.mockClear();
|
||||
});
|
||||
|
||||
it('dispose 方法', () => {
|
||||
it('dispose 方法 - 选中的节点没有被删除的', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
@ -152,16 +153,13 @@ describe('选择区测试', () => {
|
||||
const { currentDocument } = project;
|
||||
const { nodesMap, selection } = currentDocument!;
|
||||
|
||||
selection.selectAll(['form', 'node_k1ow3cbj', 'form2']);
|
||||
selection.selectAll(['form', 'node_k1ow3cbj']);
|
||||
|
||||
const selectionChangeHandler = jest.fn();
|
||||
selection.onSelectionChange(selectionChangeHandler);
|
||||
selection.dispose();
|
||||
|
||||
expect(selectionChangeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(selectionChangeHandler.mock.calls[0][0]).toEqual(['form', 'node_k1ow3cbj']);
|
||||
expect(selection.selected).toEqual(['form', 'node_k1ow3cbj']);
|
||||
selectionChangeHandler.mockClear();
|
||||
expect(selectionChangeHandler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('containsNode 方法', () => {
|
||||
@ -242,4 +240,50 @@ describe('选择区测试', () => {
|
||||
expect(selection.selected).toEqual(['page']);
|
||||
selectionChangeHandler.mockClear();
|
||||
});
|
||||
|
||||
it('getNodes', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
const { currentDocument } = project;
|
||||
const { selection } = currentDocument!;
|
||||
|
||||
selection.selectAll(['form', 'node_k1ow3cbj', 'form2']);
|
||||
|
||||
// form2 is not a valid node
|
||||
expect(selection.getNodes()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('getTopNodes - BeforeOrAfter', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
const { currentDocument } = project;
|
||||
const { selection } = currentDocument!;
|
||||
|
||||
selection.selectAll(['node_k1ow3cbj', 'node_k1ow3cbo']);
|
||||
|
||||
expect(selection.getTopNodes()).toHaveLength(2);
|
||||
});
|
||||
it('getTopNodes', () => {
|
||||
const project = new Project(designer, {
|
||||
componentsTree: [
|
||||
formSchema,
|
||||
],
|
||||
});
|
||||
project.open();
|
||||
const { currentDocument } = project;
|
||||
const { selection } = currentDocument!;
|
||||
|
||||
selection.selectAll(['node_k1ow3cbj', 'node_k1ow3cbo', 'form', 'node_k1ow3cbl', 'form2']);
|
||||
|
||||
// form2 is not a valid node, and node_k1ow3cbj is a child node of form
|
||||
expect(selection.getTopNodes()).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import { isElementNode, isDOMNodeVisible, normalizeTriggers } from '../../src/utils/misc';
|
||||
import { isElementNode, isDOMNodeVisible, normalizeTriggers, makeEventsHandler } from '../../src/utils/misc';
|
||||
|
||||
it('isElementNode', () => {
|
||||
expect(isElementNode(document.createElement('div'))).toBeTruthy();
|
||||
@ -152,3 +152,13 @@ describe('isDOMNodeVisible', () => {
|
||||
it('normalizeTriggers', () => {
|
||||
expect(normalizeTriggers(['n', 'w'])).toEqual(['N', 'W']);
|
||||
});
|
||||
|
||||
it('makeEventsHandler', () => {
|
||||
const sensor = { contentDocument: document };
|
||||
// no contentDocument
|
||||
const sensor2 = {};
|
||||
const bind = makeEventsHandler({ view: { document } } as any, [sensor, sensor2]);
|
||||
const fn = jest.fn();
|
||||
bind((doc) => fn(doc));
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@ -11,7 +11,10 @@ export class PopupPipe {
|
||||
|
||||
private currentId?: string;
|
||||
|
||||
create(props?: object): { send: (content: ReactNode, title: ReactNode) => void; show: (target: Element) => void } {
|
||||
create(props?: object): {
|
||||
send: (content: ReactNode, title: ReactNode) => void;
|
||||
show: (target: Element) => void;
|
||||
} {
|
||||
let sendContent: ReactNode = null;
|
||||
let sendTitle: ReactNode = null;
|
||||
const id = uniqueId('popup');
|
||||
@ -60,26 +63,30 @@ export class PopupPipe {
|
||||
}
|
||||
}
|
||||
|
||||
export default class PopupService extends Component<{ popupPipe?: PopupPipe; actionKey?: string; safeId?: string }> {
|
||||
export default class PopupService extends Component<{
|
||||
popupPipe?: PopupPipe;
|
||||
actionKey?: string;
|
||||
safeId?: string;
|
||||
popupContainer?: string;
|
||||
}> {
|
||||
private popupPipe = this.props.popupPipe || new PopupPipe();
|
||||
|
||||
componentWillUnmount() {
|
||||
this.popupPipe.purge();
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const { children, actionKey, safeId } = this.props;
|
||||
const { children, actionKey, safeId, popupContainer } = this.props;
|
||||
return (
|
||||
<PopupContext.Provider value={this.popupPipe}>
|
||||
{children}
|
||||
<PopupContent key={`pop${ actionKey}`} safeId={safeId} />
|
||||
<PopupContent key={`pop${actionKey}`} safeId={safeId} popupContainer={popupContainer} />
|
||||
</PopupContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class PopupContent extends PureComponent<{ safeId?: string }> {
|
||||
export class PopupContent extends PureComponent<{ safeId?: string; popupContainer?: string }> {
|
||||
static contextType = PopupContext;
|
||||
|
||||
popupContainerId = uniqueId('popupContainer');
|
||||
@ -143,11 +150,11 @@ export class PopupContent extends PureComponent<{ safeId?: string }> {
|
||||
visible={visible}
|
||||
offset={[offsetX, 0]}
|
||||
hasMask={false}
|
||||
onVisibleChange={(visible, type) => {
|
||||
onVisibleChange={(_visible, type) => {
|
||||
if (avoidLaterHidden) {
|
||||
return;
|
||||
}
|
||||
if (!visible && type === 'closeClick') {
|
||||
if (!_visible && type === 'closeClick') {
|
||||
this.setState({ visible: false });
|
||||
}
|
||||
}}
|
||||
@ -159,10 +166,11 @@ export class PopupContent extends PureComponent<{ safeId?: string }> {
|
||||
id={this.props.safeId}
|
||||
safeNode={id}
|
||||
closeable
|
||||
container={this.props.popupContainer}
|
||||
>
|
||||
<div className="lc-ballon-title">{title}</div>
|
||||
<div className="lc-ballon-content">
|
||||
<PopupService actionKey={actionKey} safeId={id}>
|
||||
<PopupService actionKey={actionKey} safeId={id} popupContainer={this.popupContainerId}>
|
||||
<ConfigProvider popupContainer={this.popupContainerId}>
|
||||
{content}
|
||||
</ConfigProvider>
|
||||
@ -170,6 +178,7 @@ export class PopupContent extends PureComponent<{ safeId?: string }> {
|
||||
</div>
|
||||
<div id={this.popupContainerId} />
|
||||
<div id="engine-variable-setter-dialog" />
|
||||
<div id="engine-popup-container" />
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -349,7 +349,6 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
||||
this.__dataHelper.getInitData()
|
||||
.then((res: any) => {
|
||||
if (isEmpty(res)) {
|
||||
this.forceUpdate();
|
||||
return resolve({});
|
||||
}
|
||||
this.setState(res, resolve as () => void);
|
||||
|
||||
687
specs/assets-spec.md
Normal file
687
specs/assets-spec.md
Normal file
@ -0,0 +1,687 @@
|
||||
# 《低代码引擎资产包协议规范》
|
||||
|
||||
# 1 介绍
|
||||
|
||||
## 1.1 本协议规范涉及的问题域
|
||||
|
||||
- 定义本协议版本号规范
|
||||
- 定义本协议中每个子规范需要被支持的 Level
|
||||
- 定义本协议相关的领域名词
|
||||
- 定义低代码资产包协议版本号规范(A)
|
||||
- 定义低代码资产包协议组件及依赖资源描述规范(A)
|
||||
- 定义低代码资产包协议组件描述资源加载规范(A)
|
||||
- 定义低代码资产包协议组件在面板展示规范(AA)
|
||||
|
||||
## 1.2 协议草案起草人
|
||||
|
||||
- 撰写:金禅、璿玑、彼洋
|
||||
- 审阅:力皓、絮黎、光弘、戊子、潕量、游鹿
|
||||
|
||||
## 1.3 版本号
|
||||
|
||||
1.1.0
|
||||
|
||||
## 1.4 协议版本号规范(A)
|
||||
|
||||
本协议采用语义版本号,版本号格式为 `major.minor.patch` 的形式。
|
||||
|
||||
- major 是大版本号:用于发布不向下兼容的协议格式修改
|
||||
- minor 是小版本号:用于发布向下兼容的协议功能新增
|
||||
- patch 是补丁号:用于发布向下兼容的协议问题修正
|
||||
|
||||
## 1.5 协议中子规范 Level 定义
|
||||
|
||||
| 规范等级 | 实现要求 |
|
||||
| -------- | ------------------------------------------------------------ |
|
||||
| A | 基础规范,低代码引擎核心层支持; |
|
||||
| AA | 推荐规范,由低代码引擎官方插件、setter 支持。 |
|
||||
| AAA | 参考规范,需由基于引擎的上层搭建平台支持,实现可参考该规范。 |
|
||||
|
||||
## 1.6 名词术语
|
||||
|
||||
- **资产包**: 低代码引擎加载资源的动态数据集合,主要包含组件及其依赖的资源、组件低代码描述、动态插件/设置器资源等。
|
||||
|
||||
## 1.7 背景
|
||||
|
||||
根据低代码引擎的实现,一个组件要在引擎上渲染和配置,需要提供组件的 umd 资源以及组件的`低代码描述`,并且组件通常都是以集合的形式被引擎消费的;除了组件之外,还有组件的依赖资源、引擎的动态插件/设置器等资源也需要注册到引擎中;因此我们定义了“低代码资产包”这个数据结构,来描述引擎所需加载的动态资源的集合。
|
||||
|
||||
## 1.8 受众
|
||||
|
||||
本协议适用于使用“低代码引擎”构建搭建平台的开发者,通过本协议的定义来进行资源的分类和加载。阅读及使用本协议,需要对低代码搭建平台的交互和实现有一定的了解,对前端开发相关技术栈的熟悉也会有帮助,协议中对通用的前端相关术语不会做进一步的解释说明。
|
||||
|
||||
# 2 协议结构
|
||||
|
||||
协议最顶层结构如下,包含 7 方面的描述内容:
|
||||
|
||||
- version { String } 当前协议版本号
|
||||
- packages { Array } 低代码编辑器中加载的资源列表
|
||||
- components { Array } 所有组件的描述协议列表
|
||||
- sort { Object } 用于描述组件面板中的 tab 和 category
|
||||
- plugins { Array } 设计器插件描述协议列表
|
||||
- setters { Array } 设计器中设置器描述协议列表
|
||||
- extConfig { Object } 平台自定义扩展字段
|
||||
|
||||
## 2.1 version(A)
|
||||
|
||||
定义当前协议 schema 的版本号;
|
||||
|
||||
| 根属性名称 | 类型 | 说明 | 变量支持 | 默认值 |
|
||||
| ---------- | ------ | ---------- | -------- | ------ |
|
||||
| version | String | 协议版本号 | - | 1.1.0 |
|
||||
|
||||
## 2.2 packages(A)
|
||||
|
||||
定义低代码编辑器中加载的资源列表,包含公共库和组件(库) cdn 资源等;
|
||||
|
||||
| 字段 | 字段描述 | 字段类型 | 规范等级 | 备注 |
|
||||
| -------------------- | --------------------------------------------------------------- | ------------- | -------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| packages[].id? | 资源唯一标识 | String | A | 资源唯一标识,如果为空,则以 package 为唯一标识 |
|
||||
| packages[].title? | 资源标题 | String | A | 资源标题 |
|
||||
| packages[].package | npm 包名 | String | A | 组件资源唯一标识 |
|
||||
| packages[].version | npm 包版本号 | String | A | 组件资源版本号 |
|
||||
| packages[].type | 资源包类型 | String | AA | 取值为: proCode(源码)、lowCode(低代码,默认为 proCode |
|
||||
| packages[].schema | 低代码组件 schema 内容 | object | AA | 取值为: proCode(源码)、lowCode(低代码) |
|
||||
| packages[].deps | 当前资源包的依赖资源的唯一标识列表 | Array<String> | A | 唯一标识为 id 或者 package 对应的值 |
|
||||
| packages[].library | 作为全局变量引用时的名称,用来定义全局变量名 | String | A | 低代码引擎通过该字段获取组件实例 |
|
||||
| packages[].editUrls | 组件编辑态视图打包后的 CDN url 列表,包含 js 和 css | Array<String> | A | 低代码引擎编辑器会加载这些 url |
|
||||
| packages[].urls | 组件渲染态视图打包后的 CDN url 列表,包含 js 和 css | Array<String> | AA | 低代码引擎渲染模块会加载这些 url |
|
||||
| packages[].advancedEditUrls | 组件多个编辑态视图打包后的 CDN url 列表集合,包含 js 和 css | Object | AAA | 上层平台根据特定标识提取某个编辑态的资源,低代码引擎编辑器会加载这些资源,优先级高于 packages[].editUrls |
|
||||
| packages[].advancedUrls | 组件多个端的渲染态视图打包后的 CDN url 列表集合,包含 js 和 css | Object | AAA | 上层平台根据特定标识提取某个渲染态的资源, 低代码引擎渲染模块会加载这些资源,优先级高于 packages[].urls |
|
||||
| packages[].external | 当前资源在作为其他资源的依赖,在其他依赖打包时时是否被排除了(同 webpack 中 external 概念) | Boolean | AAA | 某些资源会被单独提取出来,是其他依赖的前置依赖,根据这个字段决定是否提前加载该资源 |
|
||||
| packages[].loadEnv | 指定当前资源加载的环境 | Array<String> | AAA | 主要用于指定 external 资源加载的环境,取值为 design(设计态)、runtime(预览态)中的一个或多个 |
|
||||
| packages[].exportSourceId | 标识当前 package 内容是从哪个 package 导出来的 | String | AAA | 此时 urls 无效 |
|
||||
| packages[].exportSourceLibrary | 标识当前 package 是从 window 上的哪个属性导出来的 | String | AAA | exportSourceId 的优先级高于exportSourceLibrary ,此时 urls 无效 |
|
||||
| packages[].async | 标识当前 package 资源加载在 window.library 上的是否是一个异步对象 | Boolean | A | async 为 true 时,需要通过 await 才能拿到真正内容 |
|
||||
| packages[].exportMode | 标识当前 package 从其他 package 的导出方式 | String | A | 目前只支持 `"functionCall"`, exportMode等于 `"functionCall"` 时,当前package 的内容以函数的方式从其他 package 中导出,具体导出接口如: (library: string, packageName: string, isRuntime?: boolean) => any | Promise<any>, library 为当前 package 的 library, packageName 为当前的包名,返回值为当前 package 的导出内容 |
|
||||
|
||||
描述举例:
|
||||
|
||||
```json
|
||||
{
|
||||
"packages": [
|
||||
{
|
||||
"title": "fusion 组件库",
|
||||
"package": "@alifd/next",
|
||||
"version": "1.23.0",
|
||||
"urls": [
|
||||
"https://g.alicdn.com/code/lib/alifd__next/1.23.18/next.min.css",
|
||||
"https://g.alicdn.com/code/lib/alifd__next/1.23.18/next-with-locales.min.js"
|
||||
],
|
||||
"library": "Next"
|
||||
},
|
||||
{
|
||||
"title": "Fusion 精品组件库",
|
||||
"package": "@alife/fusion-ui",
|
||||
"version": "0.1.5",
|
||||
"editUrls": [
|
||||
"https://g.alicdn.com/code/npm/@alife/fusion-ui/0.1.7/build/lowcode/view.js",
|
||||
"https://g.alicdn.com/code/npm/@alife/fusion-ui/0.1.7/build/lowcode/view.css"
|
||||
],
|
||||
"urls": [
|
||||
"https://g.alicdn.com/code/npm/@alife/fusion-ui/0.1.7/dist/FusionUI.js",
|
||||
"https://g.alicdn.com/code/npm/@alife/fusion-ui/0.1.7/dist/FusionUI.css"
|
||||
],
|
||||
"library": "FusionUI"
|
||||
},
|
||||
{
|
||||
"title": "低代码组件 A",
|
||||
"id": "lcc-a",
|
||||
"version": "0.1.5",
|
||||
"type": "lowCode",
|
||||
"schema": {
|
||||
"componentsMap": [
|
||||
{
|
||||
"package": "@ali/vc-text",
|
||||
"componentName": "Text",
|
||||
"version": "4.1.1"
|
||||
}
|
||||
],
|
||||
"utils": [
|
||||
{
|
||||
"name": "dataSource",
|
||||
"type": "npm",
|
||||
"content": {
|
||||
"package": "@ali/vu-dataSource",
|
||||
"exportName": "dataSource",
|
||||
"version": "1.0.4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"componentsTree": [
|
||||
{
|
||||
"defaultProps": {
|
||||
"content": "这是默认值"
|
||||
},
|
||||
"methods": {
|
||||
"__initMethods__": {
|
||||
"compiled": "function (exports, module) { /*set actions code here*/ }",
|
||||
"source": "function (exports, module) { /*set actions code here*/ }",
|
||||
"type": "js"
|
||||
}
|
||||
},
|
||||
"loopArgs": ["item", "index"],
|
||||
"props": {
|
||||
"mobileSlot": {
|
||||
"type": "JSBlock",
|
||||
"value": {
|
||||
"children": [
|
||||
{
|
||||
"condition": true,
|
||||
"hidden": false,
|
||||
"isLocked": false,
|
||||
"conditionGroup": "",
|
||||
"componentName": "Text",
|
||||
"id": "node_ockxiczf4m2",
|
||||
"title": "",
|
||||
"props": {
|
||||
"maxLine": 0,
|
||||
"showTitle": false,
|
||||
"behavior": "NORMAL",
|
||||
"content": {
|
||||
"en-US": "Title",
|
||||
"zh-CN": "页面标题",
|
||||
"type": "i18n"
|
||||
},
|
||||
"__style__": {},
|
||||
"fieldId": "text_kxiczgj4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"componentName": "Slot",
|
||||
"props": {
|
||||
"slotName": "mobileSlot",
|
||||
"slotTitle": "mobile 容器"
|
||||
}
|
||||
}
|
||||
},
|
||||
"className": "component_k8e4naln",
|
||||
"useDevice": false,
|
||||
"fieldId": "symbol_k8bnubw4"
|
||||
},
|
||||
"condition": true,
|
||||
"children": [
|
||||
{
|
||||
"condition": true,
|
||||
"loopArgs": [null, null],
|
||||
"componentName": "Text",
|
||||
"id": "node_ockxiczf4m4",
|
||||
"props": {
|
||||
"maxLine": 0,
|
||||
"showTitle": false,
|
||||
"behavior": "NORMAL",
|
||||
"content": {
|
||||
"variable": "props.content",
|
||||
"type": "variable",
|
||||
"value": {
|
||||
"use": "zh-CN",
|
||||
"en-US": "Tips content",
|
||||
"zh-CN": "这是一个低代码组件",
|
||||
"type": "i18n"
|
||||
}
|
||||
},
|
||||
"fieldId": "text_kxid1d9n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"propTypes": [
|
||||
{
|
||||
"defaultValue": "这是默认值",
|
||||
"name": "content",
|
||||
"title": "文本内容",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"componentName": "Component",
|
||||
"id": "node_k8bnubvz",
|
||||
"state": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
"library": "LCCA"
|
||||
},
|
||||
{
|
||||
"title": "多端组件库",
|
||||
"package": "@ali/atest1",
|
||||
"version": "1.23.0",
|
||||
"advancedUrls": {
|
||||
"default": [
|
||||
"https://g.alicdn.com/legao-comp/web_bundle_0724/@alife/theme-254/1.24.0/@ali/atest1/1.0.0/theme.7c897c2.css",
|
||||
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/atest1/1.0.0/main.3354663.js"
|
||||
],
|
||||
"mobile": [
|
||||
"https://g.alicdn.com/legao-comp/web_bundle_0724/@alife/theme-254/1.24.0/@ali/atest1/1.0.0/theme.7c897c2.css",
|
||||
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/atest1/1.0.0/main.mobile.3354663.js"
|
||||
],
|
||||
"rax": [
|
||||
"https://g.alicdn.com/legao-comp/web_bundle_0724/@alife/theme-254/1.24.0/@ali/atest1/1.0.0/theme.7c897c2.css",
|
||||
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/atest1/1.0.0/main.rax.3354663.js"
|
||||
]
|
||||
},
|
||||
"advancedEditUrls": {
|
||||
"design": [
|
||||
"https://g.alicdn.com/legao-comp/web_bundle_0724/@alife/theme-254/1.24.0/@ali/atest1/1.0.0/theme.7c897c2.css",
|
||||
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/atest1/1.0.0/editView.design.js"
|
||||
],
|
||||
"default": [
|
||||
"https://g.alicdn.com/legao-comp/web_bundle_0724/@alife/theme-254/1.24.0/@ali/atest1/1.0.0/theme.7c897c2.css",
|
||||
"https://g.alicdn.com/legao-comp/web_bundle_0724/@ali/atest1/1.0.0/editView.js"
|
||||
]
|
||||
},
|
||||
"library": "Atest1"
|
||||
},
|
||||
{
|
||||
"library":"UiPaaSServerless3",
|
||||
"advancedUrls":{
|
||||
"default":[
|
||||
"https://g.alicdn.com/legao-comp/serverless3/1.1.0/env-staging-d224466e-0614-497d-8cd5-e4036dc50b70/main.js"
|
||||
]
|
||||
},
|
||||
"id":"UiPaaSServerless3-view",
|
||||
"type":"procode",
|
||||
"version":"1.0.0"
|
||||
},
|
||||
{
|
||||
"package":"react-color",
|
||||
"library":"ReactColor",
|
||||
"id":"react-color",
|
||||
"type":"procode",
|
||||
"version":"2.19.3",
|
||||
"async":true,
|
||||
"exportMode":"functionCall",
|
||||
"exportSourceId":"UiPaaSServerless3-view"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 2.3 components (A)
|
||||
|
||||
定义资产包中包含的所有组件的低代码描述的集合,分为“ComponentDescription”和“RemoteComponentDescription”(详见 2.6 TypeScript 定义):
|
||||
|
||||
- ComponentDescription: 符合“组件描述协议”的数据,详见物料规范中`2.2.2 组件描述协议`部分;
|
||||
- RemoteComponentDescription 是将一个或多个 ComponentDescription 构建打包的 js 资源的描述,在浏览器中加载该资源后可获取到其中包含的每个组件的 ComponentDescription 的具体内容;
|
||||
|
||||
## 2.4 sort (AA)
|
||||
|
||||
定义组件列表分组
|
||||
|
||||
| 根属性名称 | 类型 | 说明 | 变量支持 | 默认值 |
|
||||
| ----------------- | -------- | -------------------------------------------------------------------------------------------- | -------- | ---------------------------------------- |
|
||||
| sort.groupList | String[] | 组件分组,用于组件面板 tab 展示 | - | ['精选组件', '原子组件'] |
|
||||
| sort.categoryList | String[] | 组件面板中同一个 tab 下的不同区间用 category 区分,category 的排序依照 categoryList 顺序排列 | - | ['通用', '数据展示', '表格类', '表单类'] |
|
||||
|
||||
## 2.5 plugins (AAA)
|
||||
|
||||
自定义设计器插件列表
|
||||
|
||||
| 根属性名称 | 类型 | 说明 | 变量支持 | 默认值 |
|
||||
| --------------------- | --------- | -------------------- | -------- | ------ |
|
||||
| plugins[].name | String | 插件名称 | - | - |
|
||||
| plugins[].title | String | 插件标题 | - | - |
|
||||
| plugins[].description | String | 插件描述 | - | - |
|
||||
| plugins[].docUrl | String | 插件文档地址 | - | - |
|
||||
| plugins[].screenshot | String | 插件截图地址 | - | - |
|
||||
| plugins[].tags | String[] | 插件标签分类 | - | - |
|
||||
| plugins[].keywords | String[] | 插件检索关键字 | - | - |
|
||||
| plugins[].reference | Reference | 插件引用的资源包信息 | - | - |
|
||||
|
||||
## 2.6 setters (AAA)
|
||||
|
||||
自定义设置器列表
|
||||
|
||||
| 根属性名称 | 类型 | 说明 | 变量支持 | 默认值 |
|
||||
| --------------------- | --------- | ---------------------- | -------- | ------ |
|
||||
| setters[].name | String | 设置器组件名称 | - | - |
|
||||
| setters[].title | String | 设置器标题 | - | - |
|
||||
| setters[].description | String | 设置器描述 | - | - |
|
||||
| setters[].docUrl | String | 设置器文档地址 | - | - |
|
||||
| setters[].screenshot | String | 设置器截图地址 | - | - |
|
||||
| setters[].tags | String[] | 设置器标签分类 | - | - |
|
||||
| setters[].keywords | String[] | 设置器检索关键字 | - | - |
|
||||
| setters[].reference | Reference | 设置器引用的资源包信息 | - | - |
|
||||
|
||||
## 2.7 extConfig (AAA)
|
||||
|
||||
定义平台相关的扩展内容,用于存放平台自身实现的一些私有协议, 以允许存量平台能够平滑地迁移至标准协议。 extConfig 是一个 key-value 结构的对象,协议不会规定 extConfig 中的字段名称以及类型, 完全自定义
|
||||
|
||||
## 2.8 TypeScript 定义
|
||||
|
||||
_组件低代码描述相关部分字段含义详见物料规范中`2.2.2 组件描述协议`部分;_
|
||||
|
||||
```TypeScript
|
||||
|
||||
/**
|
||||
* 资产包协议
|
||||
*/
|
||||
export interface Assets {
|
||||
/**
|
||||
* 资产包协议版本号
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* 资源列表
|
||||
*/
|
||||
packages?: Array<Package>;
|
||||
/**
|
||||
* 所有组件的描述协议集合
|
||||
*/
|
||||
components: Array<ComponentDescription|RemoteComponentDescription>;
|
||||
/**
|
||||
* 低代码编辑器插件集合
|
||||
*/
|
||||
plugins?: Array<PluginDescription>;
|
||||
/**
|
||||
* 低代码设置器集合
|
||||
*/
|
||||
setters?: Array<SetterDescription>;
|
||||
/**
|
||||
* 平台扩展配置
|
||||
*/
|
||||
extConfig?: AssetsExtConfig;
|
||||
/**
|
||||
* 用于描述组件面板中的 tab 和 category
|
||||
*/
|
||||
sort: ComponentSort;
|
||||
}
|
||||
|
||||
export interface AssetsExtConfig{
|
||||
[index: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 描述组件面板中的 tab 和 category 排布
|
||||
*/
|
||||
export interface ComponentSort {
|
||||
/**
|
||||
* 用于描述组件面板的 tab 项及其排序,例如:["精选组件", "原子组件"]
|
||||
*/
|
||||
groupList?: String[];
|
||||
/**
|
||||
* 组件面板中同一个 tab 下的不同区间用 category 区分,category 的排序依照 categoryList 顺序排列;
|
||||
*/
|
||||
categoryList?: String[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义资产包依赖信息
|
||||
*/
|
||||
export interface Package {
|
||||
/**
|
||||
* 唯一标识
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* 包名
|
||||
*/
|
||||
package: string;
|
||||
/**
|
||||
* 包版本号
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* 资源类型
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* 组件渲染态视图打包后的 CDN url 列表,包含 js 和 css
|
||||
*/
|
||||
urls?: string[] | any;
|
||||
/**
|
||||
* 组件多个渲染态视图打包后的 CDN url 列表,包含 js 和 css,优先级高于 urls
|
||||
*/
|
||||
advancedUrls?: ComplexUrls;
|
||||
/**
|
||||
* 组件编辑态视图打包后的 CDN url 列表,包含 js 和 css
|
||||
*/
|
||||
editUrls?: string[] | any;
|
||||
/**
|
||||
* 组件多个编辑态视图打包后的 CDN url 列表,包含 js 和 css,优先级高于 editUrls
|
||||
*/
|
||||
advancedEditUrls?: ComplexUrls;
|
||||
/**
|
||||
* 低代码组件的 schema 内容
|
||||
*/
|
||||
schema?: ComponentSchema;
|
||||
/**
|
||||
* 当前资源所依赖的其他资源包的 id 列表
|
||||
*/
|
||||
deps?: string[];
|
||||
/**
|
||||
* 指定当前资源加载的环境
|
||||
*/
|
||||
loadEnv?: LoadEnv[];
|
||||
/**
|
||||
* 当前资源是否是 external 资源
|
||||
*/
|
||||
external?: boolean;
|
||||
/**
|
||||
* 作为全局变量引用时的名称,和 webpack output.library 字段含义一样,用来定义全局变量名
|
||||
*/
|
||||
library: string;
|
||||
/**
|
||||
* 组件描述导出名字,可以通过 window[exportName] 获取到组件描述的 Object 内容;
|
||||
*/
|
||||
exportName?: string;
|
||||
/**
|
||||
* 标识当前 package 资源加载在 window.library 上的是否是一个异步对象
|
||||
*/
|
||||
async?: boolean;
|
||||
/**
|
||||
* 标识当前 package 从其他 package 的导出方式
|
||||
*/
|
||||
exportMode?: string;
|
||||
/**
|
||||
* 标识当前 package 内容是从哪个 package 导出来的
|
||||
*/
|
||||
exportSourceId?: string;
|
||||
/**
|
||||
* 标识当前 package 是从 window 上的哪个属性导出来的
|
||||
*/
|
||||
exportSourceLibrary?: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 复杂 urls 结构,同时兼容简单结构和多模态结构
|
||||
*/
|
||||
export type ComplexUrls = string[] | MultiModeUrls;
|
||||
|
||||
/**
|
||||
* 多模态资源
|
||||
*/
|
||||
export interface MultiModeUrls {
|
||||
/**
|
||||
* 默认的资源 url
|
||||
*/
|
||||
default: string[];
|
||||
/**
|
||||
* 其他模态资源的 url
|
||||
*/
|
||||
[index: string]: string[];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 资源加载环境种类
|
||||
*/
|
||||
export enum LoadEnv {
|
||||
/**
|
||||
* 设计态
|
||||
*/
|
||||
design = "design",
|
||||
/**
|
||||
* 运行态
|
||||
*/
|
||||
runtime = "runtime"
|
||||
}
|
||||
|
||||
/**
|
||||
* 低代码设置器描述
|
||||
*/
|
||||
export type SetterDescription = PluginDescription;
|
||||
|
||||
/**
|
||||
* 低代码插件器描述
|
||||
*/
|
||||
export interface PluginDescription {
|
||||
/**
|
||||
* 插件名称
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 插件标题
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* 插件类型
|
||||
*/
|
||||
type?: string;
|
||||
/**
|
||||
* 插件描述
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* 插件文档地址
|
||||
*/
|
||||
docUrl: string;
|
||||
/**
|
||||
* 插件截图
|
||||
*/
|
||||
screenshot: string;
|
||||
/**
|
||||
* 插件相关的标签
|
||||
*/
|
||||
tags?: string[];
|
||||
/**
|
||||
* 插件关键字
|
||||
*/
|
||||
keywords?: string[];
|
||||
/**
|
||||
* 插件引用的资源信息
|
||||
*/
|
||||
reference: Reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源引用信息,Npm 的升级版本,
|
||||
*/
|
||||
export interface Reference {
|
||||
/**
|
||||
* 引用资源的 id 标识
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* 引用资源的包名
|
||||
*/
|
||||
package?: string;
|
||||
/**
|
||||
* 引用资源的导出对象中的属性值名称
|
||||
*/
|
||||
exportName: string;
|
||||
/**
|
||||
* 引用 exportName 上的子对象
|
||||
*/
|
||||
subName: string;
|
||||
/**
|
||||
* 引用的资源主入口
|
||||
*/
|
||||
main?: string;
|
||||
/**
|
||||
* 是否从引用资源的导出对象中获取属性值
|
||||
*/
|
||||
destructuring: boolean;
|
||||
/**
|
||||
* 资源版本号
|
||||
*/
|
||||
version: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 低代码片段
|
||||
*
|
||||
* 内容为组件不同状态下的低代码 schema (可以有多个),用户从组件面板拖入组件到设计器时会向页面 schema 中插入 snippets 中定义的组件低代码 schema
|
||||
*/
|
||||
export interface Snippet {
|
||||
title: string;
|
||||
screenshot?: string;
|
||||
schema: ElementJSON;
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件低代码描述
|
||||
*/
|
||||
export interface ComponentDescription {
|
||||
componentName: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
docUrl: string;
|
||||
screenshot: string;
|
||||
icon?: string;
|
||||
tags?: string[];
|
||||
keywords?: string[];
|
||||
devMode?: 'proCode' | 'lowCode';
|
||||
npm: Npm;
|
||||
props: Prop[];
|
||||
configure: Configure;
|
||||
/**
|
||||
* 多模态下的组件描述, 优先级高于 configure
|
||||
*/
|
||||
advancedConfigures: MultiModeConfigures;
|
||||
snippets: Snippet[];
|
||||
group: string;
|
||||
category: string;
|
||||
priority: number;
|
||||
/**
|
||||
* 组件引用的资源信息
|
||||
*/
|
||||
reference: Reference;
|
||||
}
|
||||
|
||||
export interface MultiModeConfigures {
|
||||
default: Configure;
|
||||
[index: string]: Configure;
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程物料描述
|
||||
*/
|
||||
export interface RemoteComponentDescription {
|
||||
/**
|
||||
* 组件描述导出名字,可以通过 window[exportName] 获取到组件描述的 Object 内容;
|
||||
*/
|
||||
exportName?: string;
|
||||
/**
|
||||
* 组件描述的资源链接;
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* 组件多模态描述的资源信息,优先级高于 url
|
||||
*/
|
||||
advancedUrls?: ComplexUrl;
|
||||
/**
|
||||
* 组件(库)的 npm 信息;
|
||||
*/
|
||||
package?: {
|
||||
npm?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type ComplexUrl = string | MultiModeUrl
|
||||
|
||||
export interface MultiModeUrl {
|
||||
default: string;
|
||||
[index: string]: string;
|
||||
}
|
||||
|
||||
export interface ComponentSchema {
|
||||
version: string;
|
||||
componentsMap: ComponentsMap;
|
||||
componentsTree: [ComponentTree];
|
||||
i18n: I18nMap;
|
||||
utils: UtilItem[];
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
`ComponentSchema` 的定义见[低代码业务组件描述](./1.material-spec.md#221-组件规范)
|
||||
1462
specs/lowcode-spec.md
Normal file
1462
specs/lowcode-spec.md
Normal file
File diff suppressed because it is too large
Load Diff
1821
specs/material-spec.md
Normal file
1821
specs/material-spec.md
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user