polyfill slot

This commit is contained in:
kangwei 2020-04-24 01:28:02 +08:00
parent 73a5efe8e1
commit f1b686bad5
34 changed files with 426 additions and 302 deletions

View File

@ -10,7 +10,7 @@ import {
isVertical
} from '../../designer';
import { ISimulatorHost, } from '../../simulator';
import {NodeParent } from '../../document';
import {ParentalNode } from '../../document';
import './insertion.less';
interface InsertionData {
@ -24,7 +24,7 @@ interface InsertionData {
/**
* (INode)
*/
function processChildrenDetail(sim: ISimulatorHost, container: NodeParent, detail: LocationChildrenDetail): InsertionData {
function processChildrenDetail(sim: ISimulatorHost, container: ParentalNode, detail: LocationChildrenDetail): InsertionData {
let edge = detail.edge || null;
if (!edge) {

View File

@ -2,7 +2,7 @@ import { obx, autorun, computed } from '@ali/lowcode-globals';
import { ISimulatorHost, Component, NodeInstance, ComponentInstance } from '../simulator';
import Viewport from './viewport';
import { createSimulator } from './create-simulator';
import { Node, NodeParent, DocumentModel, isNodeParent, isNode, contains, isRootNode } from '../document';
import { Node, ParentalNode, DocumentModel, isNode, contains, isRootNode } from '../document';
import ResourceConsumer from './resource-consumer';
import { AssetLevel, Asset, AssetList, assetBundle, assetItem, AssetType, getPublicPath } from '@ali/lowcode-globals';
import {
@ -21,7 +21,7 @@ import {
Rect,
CanvasPoint,
} from '../designer';
import { parseProps } from './utils/parse-props';
import { parseProps, parseMetadata } from './utils/parse-metadata';
import { isElement, hotkey } from '@ali/lowcode-globals';
import { ComponentMetadata } from '@ali/lowcode-globals';
import { BuiltinSimulatorRenderer } from './renderer';
@ -387,16 +387,19 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
const component = this.getComponent(componentName);
if (component) {
parseProps(component as any);
if (!component) {
return {
componentName,
};
}
// TODO:
// 1. generate builtin div/p/h1/h2
// 2. read propTypes
return {
componentName,
props: parseProps(this.getComponent(componentName)),
...parseMetadata(component),
};
}
@ -854,7 +857,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
container = currentRoot;
}
if (!isNodeParent(container)) {
if (!container.isParental()) {
container = container.parent || currentRoot;
}
@ -943,7 +946,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
return null;
}
isAcceptable(container: NodeParent): boolean {
isAcceptable(container: ParentalNode): boolean {
return false;
/*
const meta = container.componentMeta;
@ -1006,7 +1009,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
/**
*
*/
getNearByContainer(container: NodeParent, e: LocateEvent) {
getNearByContainer(container: ParentalNode, e: LocateEvent) {
/*
const children = container.children;
if (!children || children.length < 1) {
@ -1110,6 +1113,6 @@ function getMatched(elements: Array<Element | Text>, selector: string): Element
}
interface DropContainer {
container: NodeParent;
container: ParentalNode;
instance: ComponentInstance;
}

View File

@ -198,3 +198,10 @@ export function parseProps(component: any): PropConfig[] {
return Object.keys(result).map(key => result[key]);
}
export function parseMetadata(component: any): any {
return {
props: parseProps(component),
...component.componentMetadata,
};
}

View File

@ -11,7 +11,7 @@ import {
computed,
NestingFilter,
} from '@ali/lowcode-globals';
import { Node, NodeParent } from './document';
import { Node, ParentalNode } from './document';
import { Designer } from './designer';
import { intl } from './locale';
import { IconContainer } from './icons/container';
@ -194,7 +194,7 @@ export class ComponentMeta {
return this._transformedMetadata!;
}
checkNestingUp(my: Node | NodeData, parent: NodeParent) {
checkNestingUp(my: Node | NodeData, parent: ParentalNode) {
// 检查父子关系,直接约束型,在画布中拖拽直接掠过目标容器
if (this.parentWhitelist) {
return this.parentWhitelist(parent, my);

View File

@ -10,7 +10,7 @@ import {
autorun,
} from '@ali/lowcode-globals';
import { Project } from '../project';
import { Node, DocumentModel, insertChildren, isRootNode, NodeParent } from '../document';
import { Node, DocumentModel, insertChildren, isRootNode, ParentalNode } from '../document';
import { ComponentMeta } from '../component-meta';
import { INodeSelector, Component } from '../simulator';
import { Scroller, IScrollable } from './scroller';
@ -201,7 +201,7 @@ export class Designer {
/**
*
*/
getSuitableInsertion(): { target: NodeParent; index?: number } | null {
getSuitableInsertion(): { target: ParentalNode; index?: number } | null {
const activedDoc = this.project.currentDocument;
if (!activedDoc) {
return null;

View File

@ -88,7 +88,6 @@ export interface DragNodeObject {
export interface DragNodeDataObject {
type: DragObjectType.NodeData;
data: NodeSchema | NodeSchema[];
maps?: { [componentName: string]: string };
thumbnail?: string;
description?: string;
[extra: string]: any;
@ -233,7 +232,7 @@ export class Dragon {
const masterSensors = this.getMasterSensors();
const handleEvents = makeEventsHandler(boostEvent, masterSensors);
const newBie = !isDragNodeObject(dragObject);
const forceCopyState = isDragNodeObject(dragObject) && dragObject.nodes.some((node) => node.isSlotRoot);
const forceCopyState = isDragNodeObject(dragObject) && dragObject.nodes.some((node) => node.isSlot());
const isBoostFromDragAPI = boostEvent.type.substr(0, 4) === 'drag';
let lastSensor: ISensor | undefined;

View File

@ -1,8 +1,8 @@
import { DocumentModel, Node as ComponentNode, NodeParent } from '../document';
import { DocumentModel, Node as ComponentNode, ParentalNode } from '../document';
import { LocateEvent } from './dragon';
export interface LocationData {
target: NodeParent; // shadowNode | ConditionFlow | ElementNode | RootNode
target: ParentalNode; // shadowNode | ConditionFlow | ElementNode | RootNode
detail: LocationDetail;
source: string;
event: LocateEvent;
@ -27,7 +27,7 @@ export interface LocationChildrenDetail {
rect?: Rect;
align?: 'V' | 'H';
};
focus?: { type: 'slots' } | { type: 'node'; node: NodeParent };
focus?: { type: 'slots' } | { type: 'node'; node: ParentalNode };
}
export interface LocationPropDetail {
@ -126,7 +126,7 @@ export function getWindow(elem: Element | Document): Window {
}
export class DropLocation {
readonly target: NodeParent;
readonly target: ParentalNode;
readonly detail: LocationDetail;
readonly event: LocateEvent;
readonly source: string;

View File

@ -1,5 +1,4 @@
import {
RootSchema,
NodeData,
isJSExpression,
isDOMText,
@ -9,16 +8,26 @@ import {
autorun,
isNodeSchema,
uniqueId,
PageSchema,
ComponentSchema,
RootSchema,
} from '@ali/lowcode-globals';
import { Project } from '../project';
import { ISimulatorHost } from '../simulator';
import { ComponentMeta } from '../component-meta';
import { isDragNodeDataObject, DragNodeObject, DragNodeDataObject, DropLocation } from '../designer';
import { Node, isNodeParent, insertChildren, insertChild, NodeParent, isNode } from './node/node';
import { Node, insertChildren, insertChild, isNode, RootNode, ParentalNode } from './node/node';
import { Selection } from './selection';
import { RootNode } from './node/root-node';
import { History } from './history';
import { Prop } from './node/props/prop';
import { ExportType } from './node';
export type GetDataType<T, NodeType> = T extends undefined
? NodeType extends {
schema: infer R;
}
? R
: any
: T;
export class DocumentModel {
/**
@ -58,7 +67,7 @@ export class DocumentModel {
this.rootNode.getExtraProp('fileName', true)?.setValue(fileName);
}
private _modalNode?: NodeParent;
private _modalNode?: ParentalNode;
private _blank?: boolean;
get modalNode() {
return this._modalNode;
@ -81,8 +90,9 @@ export class DocumentModel {
this._blank = true;
}
this.rootNode = this.createRootNode(schema || {
this.rootNode = this.createNode<RootNode>(schema || {
componentName: 'Page',
id: 'root',
fileName: ''
});
@ -130,7 +140,7 @@ export class DocumentModel {
/**
* schema
*/
createNode(data: NodeData, slotFor?: Prop): Node {
createNode<T extends Node = Node, C = undefined>(data: GetDataType<C, T>): T {
let schema: any;
if (isDOMText(data) || isJSExpression(data)) {
schema = {
@ -150,14 +160,13 @@ export class DocumentModel {
// will move to another position
// todo: this.activeNodes?.push(node);
}
node.internalSetSlotFor(slotFor);
node.import(schema, true);
} else if (node) {
node = null;
}
}
if (!node) {
node = new Node(this, schema, slotFor);
node = new Node(this, schema);
// will add
// todo: this.activeNodes?.push(node);
}
@ -169,27 +178,20 @@ export class DocumentModel {
this.nodesMap.set(node.id, node);
this.nodes.add(node);
return node;
}
private createRootNode(schema: RootSchema) {
const node = new RootNode(this, schema);
this.nodesMap.set(node.id, node);
this.nodes.add(node);
return node;
return node as any;
}
/**
*
*/
insertNode(parent: NodeParent, thing: Node | NodeData, at?: number | null, copy?: boolean): Node {
insertNode(parent: ParentalNode, thing: Node | NodeData, at?: number | null, copy?: boolean): Node {
return insertChild(parent, thing, at, copy);
}
/**
*
*/
insertNodes(parent: NodeParent, thing: Node[] | NodeData[], at?: number | null, copy?: boolean) {
insertNodes(parent: ParentalNode, thing: Node[] | NodeData[], at?: number | null, copy?: boolean) {
return insertChildren(parent, thing, at, copy);
}
@ -249,7 +251,7 @@ export class DocumentModel {
return null;
}
const wrapper = this.createNode(schema);
if (isNodeParent(wrapper)) {
if (wrapper.isParental()) {
const first = nodes[0];
// TODO: check nesting rules x 2
insertChild(first.parent!, wrapper, first.index);
@ -270,11 +272,15 @@ export class DocumentModel {
}
import(schema: RootSchema, checkId = false) {
this.rootNode.import(schema, checkId);
this.rootNode.import(schema as any, checkId);
// todo: purge something
// todo: select added and active track added
}
export(exportType: ExportType = ExportType.ForSerilize) {
return this.rootNode.export(exportType);
}
/**
*
*/
@ -409,7 +415,7 @@ export class DocumentModel {
// todo:
}
checkNesting(dropTarget: NodeParent, dragObject: DragNodeObject | DragNodeDataObject): boolean {
checkNesting(dropTarget: ParentalNode, dragObject: DragNodeObject | DragNodeDataObject): boolean {
let items: Array<Node | NodeSchema>;
if (isDragNodeDataObject(dragObject)) {
items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];
@ -419,7 +425,7 @@ export class DocumentModel {
return items.every((item) => this.checkNestingDown(dropTarget, item));
}
checkDropTarget(dropTarget: NodeParent, dragObject: DragNodeObject | DragNodeDataObject): boolean {
checkDropTarget(dropTarget: ParentalNode, dragObject: DragNodeObject | DragNodeDataObject): boolean {
let items: Array<Node | NodeSchema>;
if (isDragNodeDataObject(dragObject)) {
items = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];
@ -432,7 +438,7 @@ export class DocumentModel {
/**
* parentWhitelist
*/
checkNestingUp(parent: NodeParent, obj: NodeSchema | Node): boolean {
checkNestingUp(parent: ParentalNode, obj: NodeSchema | Node): boolean {
if (isNode(obj) || isNodeSchema(obj)) {
const config = isNode(obj) ? obj.componentMeta : this.getComponentMeta(obj.componentName);
if (config) {
@ -446,7 +452,7 @@ export class DocumentModel {
/**
* childWhitelist
*/
checkNestingDown(parent: NodeParent, obj: NodeSchema | Node): boolean {
checkNestingDown(parent: ParentalNode, obj: NodeSchema | Node): boolean {
const config = parent.componentMeta;
return config.checkNestingDown(parent, obj) && this.checkNestingUp(parent, obj);
}

View File

@ -0,0 +1,5 @@
export enum ExportType {
ForRender = 1,
ForSerilize = 2,
ForSave = 3,
}

View File

@ -1,7 +1,7 @@
export * from './exclusive-group';
export * from './node';
export * from './node-children';
export * from './root-node';
export * from './props/prop';
export * from './props/prop-stash';
export * from './props/props';
export * from './export-type';

View File

@ -1,9 +1,10 @@
import { NodeData, isNodeSchema, obx, computed } from '@ali/lowcode-globals';
import { Node, NodeParent } from './node';
import { Node, ParentalNode } from './node';
import { ExportType } from './export-type';
export class NodeChildren {
@obx.val private children: Node[];
constructor(readonly owner: NodeParent, data: NodeData | NodeData[]) {
constructor(readonly owner: ParentalNode, data: NodeData | NodeData[]) {
this.children = (Array.isArray(data) ? data : [data]).map(child => {
return this.owner.document.createNode(child);
});
@ -15,10 +16,16 @@ export class NodeChildren {
/**
* schema
* @param serialize id
*/
export(serialize = false): NodeData[] {
return this.children.map(node => node.export(serialize));
export(exportType: ExportType = ExportType.ForSave): NodeData[] {
return this.children.map(node => {
const data = node.export(exportType);
if (node.isLeaf() && ExportType.ForSave === exportType) {
// FIXME: filter empty
return data.children as NodeData;
}
return data;
});
}
import(data?: NodeData | NodeData[], checkId = false) {

View File

@ -8,6 +8,9 @@ import {
TitleContent,
obx,
computed,
SlotSchema,
PageSchema,
ComponentSchema,
} from '@ali/lowcode-globals';
import { Props, EXTRA_KEY_PREFIX } from './props/props';
import { DocumentModel } from '../document-model';
@ -15,6 +18,7 @@ import { NodeChildren } from './node-children';
import { Prop } from './props/prop';
import { ComponentMeta } from '../../component-meta';
import { ExclusiveGroup, isExclusiveGroup } from './exclusive-group';
import { ExportType } from './export-type';
/**
*
@ -35,8 +39,36 @@ import { ExclusiveGroup, isExclusiveGroup } from './exclusive-group';
* locked can not select/hover/ item on canvas but can control on outline
* hidden not visible on canvas
* slotArgs like loopArgs, for slot node
*
*
*
* [Node Properties]
* componentName: Page/Block/Component
* props
* children
*
* [Root Container Extra Properties]
* fileName
* meta
* state
* defaultProps
* dataSource
* lifeCycles
* methods
* css
*
* [Directives **not used**]
* loop
* loopArgs
* condition
* ------- future support -----
* conditionGroup
* title
* ignore
* locked
* hidden
*/
export class Node {
export class Node<Schema extends NodeSchema = NodeSchema> {
/**
*
*/
@ -63,11 +95,11 @@ export class Node {
*/
readonly props: Props;
protected _children?: NodeChildren;
@obx.ref private _parent: NodeParent | null = null;
@obx.ref private _parent: ParentalNode | null = null;
/**
*
*/
get parent(): NodeParent | null {
get parent(): ParentalNode | null {
return this._parent;
}
/**
@ -100,48 +132,71 @@ export class Node {
return this.componentMeta.title;
}
get isSlotRoot(): boolean {
return this._slotFor != null;
}
isRoot() {
return (this.document.rootNode as any) == this;
}
constructor(readonly document: DocumentModel, nodeSchema: NodeSchema, slotFor?: Prop) {
constructor(readonly document: DocumentModel, nodeSchema: Schema) {
const { componentName, id, children, props, ...extras } = nodeSchema;
this.id = id || `node$${document.nextId()}`;
this.componentName = componentName;
this._slotFor = slotFor;
let _props: Props;
if (isNodeParent(this)) {
_props = new Props(this, props, extras);
this._children = new NodeChildren(this as NodeParent, children || []);
this._children.interalInitParent();
} else {
if (this.componentName === 'Leaf') {
_props = new Props(this, {
children: isDOMText(children) || isJSExpression(children) ? children : '',
});
} else {
_props = new Props(this, this.buildProps(props), extras);
this._children = new NodeChildren(this as ParentalNode, children || []);
this._children.interalInitParent();
}
this.props = _props;
}
private buildProps(props: any): any {
// TODO: run componentMeta(initials|initialValue|accessor)
return props;
}
isContainer(): boolean {
return this.isParental() && this.componentMeta.isContainer;
}
isRoot(): this is RootNode {
return this.document.rootNode == this as any;
}
isPage(): this is PageNode {
return this.isRoot() && this.componentName === 'Page';
}
isComponent(): this is ComponentNode {
return this.isRoot() && this.componentName === 'Component';
}
isSlot(): this is SlotNode {
return this._slotFor != null && this.componentName === 'Slot';
}
/**
*
*/
get isNodeParent(): boolean {
return this.componentName !== 'Leaf';
isParental(): this is ParentalNode {
return !this.isLeaf();
}
/**
*
*/
isLeaf(): this is LeafNode {
return this.componentName === 'Leaf';
}
/**
* 使
*/
internalSetParent(parent: NodeParent | null) {
internalSetParent(parent: ParentalNode | null) {
if (this._parent === parent) {
return;
}
if (this._parent && !this.isSlotRoot) {
if (!this.isSlot() && this._parent) {
this._parent.children.delete(this);
}
@ -160,6 +215,9 @@ export class Node {
this._slotFor = slotFor;
}
/**
*
*/
get slotFor() {
return this._slotFor;
}
@ -168,7 +226,7 @@ export class Node {
*
*/
remove() {
if (this.parent && !this.isSlotRoot) {
if (!this.isSlot() && this.parent) {
this.parent.children.delete(this, true);
}
}
@ -199,17 +257,13 @@ export class Node {
}
@computed get propsData(): PropsMap | PropsList | null {
if (!this.isNodeParent || this.componentName === 'Fragment') {
if (!this.isParental() || this.componentName === 'Fragment') {
return null;
}
return this.props.export(true).props || null;
return this.props.export(ExportType.ForSerilize).props || null;
}
isContainer() {
return this.isNodeParent && this.componentMeta.isContainer;
}
@computed isSlotContainer() {
@computed hasSlots() {
for (const item of this.props) {
if (item.type === 'slot') {
return true;
@ -280,11 +334,11 @@ export class Node {
return v != null && v !== '';
}
wrapWith(schema: NodeSchema) {
wrapWith(schema: Schema) {
// todo
}
replaceWith(schema: NodeSchema, migrate = true) {
replaceWith(schema: Schema, migrate = true) {
// reuse the same id? or replaceSelection
//
}
@ -366,22 +420,18 @@ export class Node {
/**
* - schema
*/
get schema(): NodeSchema {
// FIXME! serilize?
// for design - pass to Renderer
// for save production data
// for serilize mutation record
return this.export(true);
get schema(): Schema {
return this.export(ExportType.ForSave);
}
set schema(data: NodeSchema) {
set schema(data: Schema) {
this.import(data);
}
import(data: NodeSchema, checkId = false) {
import(data: Schema, checkId = false) {
const { componentName, id, children, props, ...extras } = data;
if (isNodeParent(this)) {
if (this.isParental()) {
this.props.import(props, extras);
(this._children as NodeChildren).import(children, checkId);
} else {
@ -391,32 +441,32 @@ export class Node {
/**
* schema
* @param serialize id
*/
export(serialize = false): NodeSchema {
export(exportType: ExportType = ExportType.ForSave): Schema {
// run transducers
// run
const baseSchema: any = {
componentName: this.componentName === 'Leaf' ? 'Fragment' : this.componentName,
componentName: this.componentName,
};
if (serialize) {
if (exportType !== ExportType.ForSave) {
baseSchema.id = this.id;
}
if (!isNodeParent(this)) {
baseSchema.children = this.props.get('children')?.export(serialize);
// FIXME!
return baseSchema.children;
if (this.isLeaf()) {
baseSchema.children = this.props.get('children')?.export(exportType);
return baseSchema;
}
const { props = {}, extras } = this.props.export(serialize) || {};
const { props = {}, extras } = this.props.export(exportType) || {};
const schema: any = {
...baseSchema,
props,
...extras,
};
if (this.children.size > 0) {
schema.children = this.children.export(serialize);
if (this.isParental() && this.children.size > 0) {
schema.children = this.children.export(exportType);
}
return schema;
@ -468,7 +518,7 @@ export class Node {
return;
}
this.purged = true;
if (isNodeParent(this)) {
if (this.isParental()) {
this.children.purge();
}
this.props.purge();
@ -510,7 +560,7 @@ export class Node {
/**
* @deprecated
*/
getDOMNode() {
getDOMNode(): any {
const instance = this.document.simulator?.getComponentInstances(this)?.[0];
if (!instance) {
return;
@ -535,17 +585,24 @@ export class Node {
}
}
export interface NodeParent extends Node {
export interface ParentalNode<T extends NodeSchema = NodeSchema> extends Node<T> {
readonly children: NodeChildren;
readonly props: Props;
}
export interface LeafNode extends Node {
readonly children: null;
}
export interface SlotNode extends ParentalNode<SlotSchema> {}
export interface PageNode extends ParentalNode<PageSchema> {}
export interface ComponentNode extends ParentalNode<ComponentSchema> {}
export type RootNode = PageNode | ComponentNode;
export function isNode(node: any): node is Node {
return node && node.isNode;
}
export function isNodeParent(node: Node): node is NodeParent {
return node.isNodeParent;
export function isRootNode(node: Node): node is RootNode {
return node && node.isRoot();
}
export function getZLevelTop(child: Node, zLevel: number): Node | null {
@ -568,7 +625,7 @@ export function contains(node1: Node, node2: Node): boolean {
return true;
}
if (!node1.isNodeParent || !node2.parent) {
if (!node1.isParental || !node2.parent) {
return false;
}
@ -617,10 +674,10 @@ export function comparePosition(node1: Node, node2: Node): PositionNO {
return PositionNO.BeforeOrAfter;
}
export function insertChild(container: NodeParent, thing: Node | NodeData, at?: number | null, copy?: boolean): Node {
export function insertChild(container: ParentalNode, thing: Node | NodeData, at?: number | null, copy?: boolean): Node {
let node: Node;
if (isNode(thing) && (copy || thing.isSlotRoot)) {
thing = thing.export(false);
if (isNode(thing) && (copy || thing.isSlot())) {
thing = thing.export(ExportType.ForSave);
}
if (isNode(thing)) {
node = thing;
@ -634,7 +691,7 @@ export function insertChild(container: NodeParent, thing: Node | NodeData, at?:
}
export function insertChildren(
container: NodeParent,
container: ParentalNode,
nodes: Node[] | NodeData[],
at?: number | null,
copy?: boolean,

View File

@ -2,11 +2,11 @@ import {
CompositeValue,
isJSExpression,
isJSSlot,
NodeData,
isNodeSchema,
untracked,
computed,
obx
obx,
JSSlot,
SlotSchema
} from '@ali/lowcode-globals';
import { uniqueId } from '@ali/lowcode-globals';
import { isPlainObject } from '@ali/lowcode-globals';
@ -14,7 +14,8 @@ import { hasOwnProperty } from '@ali/lowcode-globals';
import { PropStash } from './prop-stash';
import { valueToSource } from './value-to-source';
import { Props } from './props';
import { Node } from '../node';
import { SlotNode } from '../node';
import { ExportType } from '../export-type';
export const UNSET = Symbol.for('unset');
export type UNSET = typeof UNSET;
@ -45,10 +46,10 @@ export class Prop implements IPropParent {
*
*/
@computed get value(): CompositeValue | UNSET {
return this.export(true);
return this.export(ExportType.ForSerilize);
}
export(serialize = false): CompositeValue | UNSET {
export(exporType: ExportType = ExportType.ForSave): CompositeValue | UNSET {
const type = this._type;
if (type === 'unset') {
@ -60,9 +61,18 @@ export class Prop implements IPropParent {
}
if (type === 'slot') {
const schema = this._slotNode!.export(exporType);
if (exporType === ExportType.ForSave) {
return {
type: 'JSSlot',
params: schema.params,
value: schema.children,
};
}
return {
type: 'JSSlot',
value: this._slotNode!.export(serialize),
params: schema.params,
value: schema,
};
}
@ -72,7 +82,7 @@ export class Prop implements IPropParent {
}
const maps: any = {};
this.items!.forEach((prop, key) => {
const v = prop.export(serialize);
const v = prop.export(exporType);
if (v !== UNSET) {
maps[key] = v;
}
@ -85,7 +95,7 @@ export class Prop implements IPropParent {
return this._value;
}
return this.items!.map((prop) => {
const v = prop.export(serialize);
const v = prop.export(exporType);
return v === UNSET ? null : v;
});
}
@ -103,7 +113,7 @@ export class Prop implements IPropParent {
}
// todo: JSFunction ...
if (this.type === 'slot') {
return JSON.stringify(this._slotNode!.export(false));
return JSON.stringify(this._slotNode!.export(ExportType.ForSave));
}
return this._code != null ? this._code : JSON.stringify(this.value);
}
@ -161,7 +171,7 @@ export class Prop implements IPropParent {
this._type = 'list';
} else if (isPlainObject(val)) {
if (isJSSlot(val)) {
this.setAsSlot(val.value);
this.setAsSlot(val);
return;
}
if (isJSExpression(val)) {
@ -181,7 +191,7 @@ export class Prop implements IPropParent {
}
@computed getValue(): CompositeValue {
const v = this.export(true);
const v = this.export(ExportType.ForSerilize);
if (v === UNSET) {
return null;
}
@ -204,24 +214,25 @@ export class Prop implements IPropParent {
}
}
private _slotNode?: Node;
private _slotNode?: SlotNode;
get slotNode() {
return this._slotNode;
}
setAsSlot(data: NodeData) {
setAsSlot(data: JSSlot) {
this._type = 'slot';
if (
this._slotNode &&
isNodeSchema(data) &&
(!data.id || this._slotNode.id === data.id) &&
this._slotNode.componentName === data.componentName
) {
this._slotNode.import(data);
const slotSchema: SlotSchema = {
componentName: 'Slot',
title: data.title,
params: data.params,
children: data.value,
};
if (this._slotNode) {
this._slotNode.import(slotSchema);
} else {
this._slotNode?.internalSetParent(null);
const owner = this.props.owner;
this._slotNode = owner.document.createNode(data, this);
this._slotNode = owner.document.createNode<SlotNode>(slotSchema);
this._slotNode.internalSetParent(owner as any);
this._slotNode.internalSetSlotFor(this);
}
this.dispose();
}

View File

@ -2,6 +2,7 @@ import { PropsMap, PropsList, CompositeValue, computed, obx, uniqueId } from '@a
import { PropStash } from './prop-stash';
import { Prop, IPropParent, UNSET } from './prop';
import { Node } from '../node';
import { ExportType } from '../export-type';
export const EXTRA_KEY_PREFIX = '__';
@ -79,7 +80,7 @@ export class Props implements IPropParent {
});
}
export(serialize = false): { props?: PropsMap | PropsList; extras?: object } {
export(exportType: ExportType = ExportType.ForSave): { props?: PropsMap | PropsList; extras?: object } {
if (this.items.length < 1) {
return {};
}
@ -88,7 +89,7 @@ export class Props implements IPropParent {
if (this.type === 'list') {
props = [];
this.items.forEach(item => {
let value = item.export(serialize);
let value = item.export(exportType);
if (value === UNSET) {
value = null;
}
@ -111,7 +112,7 @@ export class Props implements IPropParent {
// todo ...spread
return;
}
let value = item.export(serialize);
let value = item.export(exportType);
if (value === UNSET) {
value = null;
}

View File

@ -1,81 +0,0 @@
import { RootSchema } from '@ali/lowcode-globals';
import { Node, NodeParent } from './node';
import { DocumentModel } from '../document-model';
import { NodeChildren } from './node-children';
/**
*
*
* [Node Properties]
* componentName: Page/Block/Component
* props
* children
*
* [Root Container Extra Properties]
* fileName
* meta
* state
* defaultProps
* dataSource
* lifeCycles
* methods
* css
*
* [Directives **not used**]
* loop
* loopArgs
* condition
* ------- future support -----
* conditionGroup
* title
* ignore
* locked
* hidden
*/
export class RootNode extends Node implements NodeParent {
readonly isRootNode = true;
get isNodeParent() {
return true;
}
get index() {
return 0;
}
get nextSibling() {
return null;
}
get prevSibling() {
return null;
}
get zLevel() {
return 0;
}
get parent() {
return null;
}
get children(): NodeChildren {
return this._children as NodeChildren;
}
internalSetParent(parent: null) {
// empty
}
constructor(readonly document: DocumentModel, rootSchema: RootSchema) {
super(document, rootSchema);
}
isPage() {
return this.componentName === 'Page';
}
isComponent() {
return this.componentName === 'Component';
}
isBlock() {
return this.componentName === 'Block';
}
}
export function isRootNode(node: any): node is RootNode {
return node && node.isRootNode;
}

View File

@ -13,6 +13,13 @@ export interface NodeSchema {
loop?: CompositeValue;
loopArgs?: [string, string];
children?: NodeData | NodeData[];
// ------- future support -----
conditionGroup?: string;
title?: string;
ignore?: boolean;
locked?: boolean;
hidden?: boolean;
}
export type PropsMap = CompositeObject;
@ -30,7 +37,7 @@ export function isDOMText(data: any): data is DOMText {
export type DOMText = string;
export interface RootSchema extends NodeSchema {
export interface ContainerSchema extends NodeSchema {
componentName: string; // 'Block' | 'Page' | 'Component';
fileName: string;
meta?: object;
@ -48,18 +55,24 @@ export interface RootSchema extends NodeSchema {
defaultProps?: CompositeObject;
}
export interface BlockSchema extends RootSchema {
componentName: 'Block';
}
export interface PageSchema extends RootSchema {
export interface PageSchema extends ContainerSchema {
componentName: 'Page';
}
export interface ComponentSchema extends RootSchema {
export interface ComponentSchema extends ContainerSchema {
componentName: 'Component';
}
export type RootSchema = PageSchema | ComponentSchema;
export interface BlockSchema extends NodeSchema {
componentName: 'Block';
}
export interface SlotSchema extends NodeSchema {
componentName: 'Slot';
params?: string[];
}
export interface ProjectSchema {
version: string;
componentsMap: ComponentsMap;

View File

@ -1,4 +1,4 @@
import { NodeSchema } from './schema';
import { NodeSchema, NodeData } from './schema';
// 表达式
export interface JSExpression {
@ -15,9 +15,10 @@ export interface JSExpression {
export interface JSSlot {
type: 'JSSlot';
title?: string;
// 函数的入参
params?: string[];
value: NodeSchema[];
value?: NodeData[] | NodeData;
}
// JSON 基本类型

View File

@ -1,16 +1,16 @@
import { NodeParent, DropLocation, isLocationChildrenDetail, LocateEvent } from '@ali/lowcode-designer';
import { ParentalNode, DropLocation, isLocationChildrenDetail, LocateEvent } from '@ali/lowcode-designer';
/**
*
*/
export default class DwellTimer {
private timer: number | undefined;
private previous?: NodeParent;
private previous?: ParentalNode;
private event?: LocateEvent;
constructor(private decide: (node: NodeParent, event: LocateEvent) => void, private timeout: number = 500) {}
constructor(private decide: (node: ParentalNode, event: LocateEvent) => void, private timeout: number = 500) {}
focus(node: NodeParent, event: LocateEvent) {
focus(node: ParentalNode, event: LocateEvent) {
this.event = event;
if (this.previous === node) {
return;

View File

@ -1,4 +1,4 @@
import { DropLocation, NodeParent, isLocationChildrenDetail } from '@ali/lowcode-designer';
import { DropLocation, ParentalNode, isLocationChildrenDetail } from '@ali/lowcode-designer';
const IndentSensitive = 15;
export class IndentTrack {
@ -6,7 +6,7 @@ export class IndentTrack {
reset() {
this.indentStart = null;
}
getIndentParent(lastLoc: DropLocation, loc: DropLocation): [NodeParent, number] | null {
getIndentParent(lastLoc: DropLocation, loc: DropLocation): [ParentalNode, number] | null {
if (
lastLoc.target !== loc.target ||
!isLocationChildrenDetail(lastLoc.detail) ||
@ -33,7 +33,7 @@ export class IndentTrack {
const index = loc.detail.index;
if (direction === 'left') {
if (!parent.parent || parent.isSlotRoot || index < parent.children.size) {
if (parent.isSlot() || !parent.parent || index < parent.children.size) {
return null;
}
return [parent.parent, parent.index + 1];

View File

@ -13,7 +13,7 @@ import {
isLocationChildrenDetail,
LocationChildrenDetail,
LocationDetailType,
NodeParent,
ParentalNode,
contains,
Node,
} from '@ali/lowcode-designer';
@ -199,7 +199,7 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable {
let index: any;
let focus: any;
let valid = true;
if (target.isSlotContainer()) {
if (target.hasSlots()) {
index = null;
focus = { type: 'slots' };
} else {
@ -301,7 +301,7 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable {
if (focusSlots) {
this.dwell.reset();
return designer.createLocation({
target: node as NodeParent,
target: node as ParentalNode,
source: this.id,
event: e,
detail: {
@ -342,9 +342,9 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable {
index = node.index;
}
if (node.isSlotRoot) {
if (node.isSlot()) {
// 是个插槽根节点
if (!treeNode.isContainer() && !treeNode.isSlotContainer()) {
if (!treeNode.isContainer() && !treeNode.hasSlots()) {
return designer.createLocation({
target: node.parent!,
source: this.id,
@ -377,7 +377,7 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable {
let focusNode: Node | undefined;
// focus
if (!expanded && (treeNode.isContainer() || treeNode.isSlotContainer())) {
if (!expanded && (treeNode.isContainer() || treeNode.hasSlots())) {
focusNode = node;
}
@ -439,7 +439,7 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable {
return null;
}
const container = treeNode.node as NodeParent;
const container = treeNode.node as ParentalNode;
const detail: LocationChildrenDetail = {
type: LocationDetailType.Children,
};
@ -449,10 +449,10 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable {
source: this.id,
event: e,
};
const isSlotContainer = treeNode.isSlotContainer();
const isSlotContainer = treeNode.hasSlots();
const isContainer = treeNode.isContainer();
if (container.isSlotRoot && !treeNode.expanded) {
if (container.isSlot() && !treeNode.expanded) {
// 未展开,直接定位到内部第一个节点
if (isSlotContainer) {
detail.index = null;
@ -693,7 +693,7 @@ export class OutlineMain implements ISensor, IScrollBoard, IScrollable {
}
}
function checkRecursion(parent: Node | undefined | null, dragObject: DragObject): parent is NodeParent {
function checkRecursion(parent: Node | undefined | null, dragObject: DragObject): parent is ParentalNode {
if (!parent) {
return false;
}

View File

@ -11,7 +11,7 @@ export default class TreeNode {
*
*/
@computed get expandable(): boolean {
return this.hasChildren() || this.isSlotContainer() || this.dropDetail?.index != null;
return this.hasChildren() || this.hasSlots() || this.dropDetail?.index != null;
}
/**
@ -169,51 +169,17 @@ export default class TreeNode {
/**
* "插槽"
*/
isSlotContainer(): boolean {
return this.node.isSlotContainer();
hasSlots(): boolean {
return this.node.hasSlots();
}
hasChildren(): boolean {
return this.isContainer() && this.node.children?.notEmpty() ? true : false;
}
/*
get xForValue() {
const node = this.node;
return isElementNode(node) && node.xforValue ? node.xforValue : null;
}
get flowHidden() {
return (this.node as ElementNode).flowHidden;
}
get flowIndex() {
return (this.node as ElementNode).flowIndex;
}
get conditionFlow() {
return (this.node as ElementNode).conditionFlow;
}
hasXIf() {
return hasConditionFlow(this.node);
}
hasXFor() {
const node = this.node;
return isElementNode(node) && node.xforFn;
}
*/
select(isMulti: boolean) {
const node = this.node;
/*
if (this.hasXIf()) {
(node as ElementNode).setFlowVisible();
}
*/
const selection = node.document.selection;
if (isMulti) {
selection.add(node.id);

View File

@ -108,7 +108,7 @@ class TreeNodeSlots extends Component<{
}
render() {
const { treeNode } = this.props;
if (!treeNode.isSlotContainer()) {
if (!treeNode.hasSlots()) {
return null;
}
return (

View File

@ -66,7 +66,7 @@ export default class TreeTitle extends Component<{
const { editing } = this.state;
const isCNode = !treeNode.isRoot();
const { node } = treeNode;
const isNodeParent = node.isNodeParent;
const isNodeParent = node.isParental();
let style: any;
if (isCNode) {
const depth = treeNode.depth;

View File

@ -17,7 +17,7 @@
}
}
}
>.next-input {
.next-input,.next-date-picker {
width: 100%;
}
&.lc-block-setter {

View File

@ -386,6 +386,7 @@ export default class BaseEngine extends PureComponent {
__parseProps = (props, self, path, info) => {
const { schema, Comp, componentInfo = {} } = info;
const propInfo = getValue(componentInfo.props, path);
// FIXME! 线
const propType = propInfo && propInfo.extra && propInfo.extra.propType;
const ignoreParse = schema.__ignoreParse || [];
const checkProps = (value) => {

View File

@ -0,0 +1,23 @@
import { Component } from 'react';
class Leaf extends Component {
static displayName = 'Leaf';
static componentMetadata = {
componentName: 'Leaf',
configure: {
props: [{
name: 'children',
setter: 'StringSetter',
}],
// events/className/style/general/directives
supports: false,
}
};
render() {
const { children } = this.props;
return children;
}
}
export default Leaf;

View File

@ -0,0 +1,53 @@
import { Component } from 'react';
class Slot extends Component {
static displayName = 'Slot';
static componentMetadata = {
componentName: 'Slot',
configure: {
props: [{
name: '___title___',
title: {
type: 'i18n',
'en-US': 'Slot Title',
'zh-CN': '插槽标题'
},
setter: 'StringSetter',
defaultValue: '插槽容器'
}, {
name: '___params___',
title: {
type: 'i18n',
'en-US': 'Slot Params',
'zh-CN': '插槽入参'
},
setter: {
componentName: 'ArraySetter',
props: {
itemSetter: {
componentName: 'StringSetter',
props: {
placeholder: {
type: 'i18n',
'zh-CN': '参数名称',
'en-US': 'Argument Name'
}
}
}
}
}
}],
// events/className/style/general/directives
supports: false,
}
};
render() {
const { children } = this.props;
return (
<div className="lc-container">{children}</div>
);
}
}
export default Slot;

View File

@ -41,6 +41,7 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> {
}
render() {
const { renderer } = this.props;
console.info(renderer.schema)
return (
<LowCodeRenderer
schema={renderer.schema}

View File

@ -57,6 +57,29 @@ html.engine-blur #engine {
-webkit-filter: blur(4px);
}
.lc-container {
&:empty {
background: #f2f3f5;
color: #a7b1bd;
outline: 1px dashed rgba(31, 56, 88, 0.2);
outline-offset: -1px !important;
height: 66px;
max-height: 100%;
min-width: 140px;
text-align: center;
overflow: hidden;
display: flex;
align-items: center;
&:before {
content: '\62D6\62FD\7EC4\4EF6\6216\6A21\677F\5230\8FD9\91CC';
font-size: 14px;
z-index: 1;
width: 100%;
white-space: nowrap;
}
}
}
.engine-empty {
background: #f2f3f5;
color: #a7b1bd;

View File

@ -13,6 +13,8 @@ import { cursor } from '@ali/lowcode-globals';
import { setNativeSelection } from '@ali/lowcode-globals';
import { RootSchema, NpmInfo } from '@ali/lowcode-globals';
import { BuiltinSimulatorRenderer, NodeInstance } from '@ali/lowcode-designer';
import Slot from './builtin-components/slot';
import Leaf from './builtin-components/leaf';
export class SimulatorRenderer implements BuiltinSimulatorRenderer {
readonly isSimulatorRenderer = true;
@ -25,7 +27,7 @@ export class SimulatorRenderer implements BuiltinSimulatorRenderer {
// sync layout config
// sync schema
this._schema = host.document.schema;
this._schema = host.document.export(1);
// todo: split with others, not all should recompute
if (this._libraryMap !== host.libraryMap || this._componentsMap !== host.designer.componentsMap) {
@ -311,8 +313,17 @@ export interface LibraryMap {
[key: string]: string;
}
// Slot/Leaf and Fragment|FunctionComponent polyfill(ref)
const builtinComponents = {
Slot,
Leaf,
};
function buildComponents(libraryMap: LibraryMap, componentsMap: { [componentName: string]: NpmInfo | ComponentType<any> }) {
const components: any = {};
const components: any = {
...builtinComponents
};
Object.keys(componentsMap).forEach((componentName) => {
let component = componentsMap[componentName];
if (isReactComponent(component)) {

View File

@ -304,13 +304,24 @@ export function upgradePropConfig(config: OldPropConfig) {
extraProps.defaultValue = initialValue;
}
const initialFn = initial || initialValue;
let initialFn = initial || initialValue;
if (accessor) {
extraProps.getValue = (field: Field, fieldValue: any) => {
return accessor.call(field, fieldValue);
};
if (!initialFn) {
// FIXME!
initialFn
}
}
extraProps.initialValue = (field: Field, defaultValue?: any) => {
if (defaultValue === undefined) {
defaultValue = extraProps.defaultValue;
}
if (typeof initialFn === 'function') {
// ?
return initialFn(null, defaultValue);
}
@ -325,11 +336,6 @@ export function upgradePropConfig(config: OldPropConfig) {
}
}
}
if (accessor) {
extraProps.getValue = (field: Field, fieldValue: any) => {
return accessor.call(field, fieldValue);
};
}
if (mutator) {
extraProps.setValue = (field: Field, value: any) => {
mutator.call(field, value);
@ -535,9 +541,17 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
experimental.context = context;
}
if (snippets) {
experimental.snippets = snippets;
}
if (defaultProps || initialChildren) {
experimental.snippets = snippets.map(data => {
const { schema = {} } = data;
if (initialChildren && !schema.children) {
schema.children = initialChildren;
}
return {
...data,
schema,
};
});
} else if (defaultProps || initialChildren) {
const snippet = {
screenshot: icon,
label: title,

View File

@ -19,6 +19,7 @@ export class Bus {
// alias to unsub
off(event: string, func: (...args: any[]) => any) {
this.unsub(event, func);
}
// alias to pub

View File

@ -1,5 +1,5 @@
import { designer } from './editor';
import { DragObjectType, isNode } from '@ali/lowcode-designer';
import { DragObjectType, isNode, ExportType } from '@ali/lowcode-designer';
const dragon = designer.dragon;
const DragEngine = {
@ -12,14 +12,16 @@ const DragEngine = {
if (isNode(r)) {
return {
type: DragObjectType.NodeData,
data: r.export(false),
data: r.export(ExportType.ForSave),
};
// FIXME! designer has bug
/*
return {
type: DragObjectType.Node,
nodes: [r],
};*/
};
*/
} else {
return {
type: DragObjectType.NodeData,