can load legao assets

This commit is contained in:
kangwei 2020-04-20 14:50:22 +08:00
parent 469c26985b
commit 208bccfcb9
28 changed files with 1115 additions and 93 deletions

View File

@ -9,6 +9,7 @@ import {
getRegisteredMetadataTransducers,
registerMetadataTransducer,
computed,
NestingFilter,
} from '@ali/lowcode-globals';
import { Node, NodeParent } from './document';
import { Designer } from './designer';
@ -24,6 +25,9 @@ function ensureAList(list?: string | string[]): string[] | null {
return null;
}
if (!Array.isArray(list)) {
if (typeof list !== 'string') {
return null;
}
list = list.split(/ *[ ,|] */).filter(Boolean);
}
if (list.length < 1) {
@ -32,6 +36,27 @@ function ensureAList(list?: string | string[]): string[] | null {
return list;
}
function isRegExp(obj: any): obj is RegExp {
return obj && obj.test && obj.exec && obj.compile;
}
function buildFilter(rule?: string | string[] | RegExp | NestingFilter) {
if (!rule) {
return null;
}
if (typeof rule === 'function') {
return rule;
}
if (isRegExp(rule)) {
return (testNode: Node | NodeSchema) => rule.test(testNode.componentName);
}
const list = ensureAList(rule);
if (!list) {
return null;
}
return (testNode: Node | NodeSchema) => list.includes(testNode.componentName);
}
export class ComponentMeta {
readonly isComponentMeta = true;
private _npm?: NpmInfo;
@ -64,8 +89,8 @@ export class ComponentMeta {
return config?.combined || config?.props || [];
}
private parentWhitelist?: string[] | null;
private childWhitelist?: string[] | null;
private parentWhitelist?: NestingFilter | null;
private childWhitelist?: NestingFilter | null;
private _title?: TitleContent;
get title() {
@ -123,8 +148,8 @@ export class ComponentMeta {
this._rectSelector = component.rectSelector;
if (component.nestingRule) {
const { parentWhitelist, childWhitelist } = component.nestingRule;
this.parentWhitelist = ensureAList(parentWhitelist);
this.childWhitelist = ensureAList(childWhitelist);
this.parentWhitelist = buildFilter(parentWhitelist);
this.childWhitelist = buildFilter(childWhitelist);
}
} else {
this._isContainer = false;
@ -172,7 +197,7 @@ export class ComponentMeta {
checkNestingUp(my: Node | NodeData, parent: NodeParent) {
// 检查父子关系,直接约束型,在画布中拖拽直接掠过目标容器
if (this.parentWhitelist) {
return this.parentWhitelist.includes(parent.componentName);
return this.parentWhitelist(parent, my);
}
return true;
}
@ -180,7 +205,7 @@ export class ComponentMeta {
checkNestingDown(my: Node, target: Node | NodeSchema) {
// 检查父子关系,直接约束型,在画布中拖拽直接掠过目标容器
if (this.childWhitelist) {
return this.childWhitelist.includes(target.componentName);
return this.childWhitelist(target, my);
}
return true;
}

View File

@ -12,7 +12,7 @@ import {
import { Project } from '../project';
import { Node, DocumentModel, insertChildren, isRootNode, NodeParent } from '../document';
import { ComponentMeta } from '../component-meta';
import { INodeSelector } from '../simulator';
import { INodeSelector, Component } from '../simulator';
import { Scroller, IScrollable } from './scroller';
import { Dragon, isDragNodeObject, isDragNodeDataObject, LocateEvent, DragObject } from './dragon';
import { ActiveTracker } from './active-tracker';
@ -306,6 +306,7 @@ export class Designer {
private _lostComponentMetasMap = new Map<string, ComponentMeta>();
private buildComponentMetasMap(metas: ComponentMetadata[]) {
console.info(this._componentMetasMap);
metas.forEach((data) => this.createComponentMeta(data));
}
@ -352,10 +353,13 @@ export class Designer {
return meta;
}
@computed get componentsMap(): { [key: string]: NpmInfo } {
@computed get componentsMap(): { [key: string]: NpmInfo | Component } {
const maps: any = {};
this._componentMetasMap.forEach((config, key) => {
if (config.npm) {
const view = config.getMetadata().experimental?.view;
if (view) {
maps[key] = view;
} else if (config.npm) {
maps[key] = config.npm;
}
});

View File

@ -466,6 +466,36 @@ export class Node {
this.props.purge();
this.document.internalRemoveAndPurgeNode(this);
}
// ======= compatibles ====
isEmpty(): boolean {
return this.children?.isEmpty() || true;
}
getStatus() {
return 'default';
}
setStatus() {
}
getDOMNode() {
const instance = this.document.simulator?.getComponentInstances(this)?.[0];
if (!instance) {
return;
}
return this.document.simulator?.findDOMNodes(instance)?.[0];
}
getChildren() {
return this.children;
}
getPage() {
return this.document;
}
getComponentName() {
return this.componentName;
}
insertBefore(node: Node, ref?: Node) {
this.children?.insert(node, ref ? ref.index : null);
}
}
export interface NodeParent extends Node {

View File

@ -109,7 +109,7 @@ export interface I18nConfig {
export type I18nFunction = (key: string, params: any) => string;
export interface Utils {
[key: string]: (...args: []) => any;
[key: string]: (...args: any[]) => any;
}
export interface PluginProps {

View File

@ -35,8 +35,8 @@ export default class DesignerPlugin extends PureComponent<PluginProps, DesignerP
}
const { components, packages } = assets;
const state = {
componentMetadatas: components ? components.filter(item => item.type === 'ComponentMetadata') : [],
library: packages ? Object.values(packages) : [],
componentMetadatas: components || [],
library: packages || [],
};
this.setState(state);
};
@ -63,6 +63,8 @@ export default class DesignerPlugin extends PureComponent<PluginProps, DesignerP
return null;
}
console.info('metadatas', componentMetadatas);
return (
<DesignerView
onMount={this.handleDesignerMount}

View File

@ -153,6 +153,9 @@
& > svg {
width: 16px;
height: 16px;
* {
fill: var(--color-icon-normal, rgba(31, 56, 88, 0.4));
}
}
}

View File

@ -32,7 +32,7 @@ export default class SettingsMainView extends Component {
if (this.main.isMulti) {
return (
<div className="lc-settings-navigator">
{createIcon(this.main.componentMeta?.icon)}
{createIcon(this.main.componentMeta?.icon, { className: 'lc-settings-navigator-icon'})}
<Title title={this.main.componentMeta!.title} />
<span>x {this.main.nodes.length}</span>
</div>
@ -57,7 +57,7 @@ export default class SettingsMainView extends Component {
return (
<div className="lc-settings-navigator">
{createIcon(this.main.componentMeta?.icon)}
{createIcon(this.main.componentMeta?.icon, { className: 'lc-settings-navigator-icon'})}
<Breadcrumb className="lc-settings-node-breadcrumb">{items}</Breadcrumb>
</div>
);

View File

@ -4,6 +4,7 @@ import { ComponentMeta, Node, Designer, Selection } from '@ali/lowcode-designer'
import { TitleContent, FieldExtraProps, SetterType, CustomView, FieldConfig, isCustomView } from '@ali/lowcode-globals';
import { getTreeMaster } from '@ali/lowcode-plugin-outline-pane';
import Editor from '@ali/lowcode-editor-core';
import { Transducer } from './utils';
export interface SettingTarget {
// 所设置的节点集,至少一个
@ -91,6 +92,7 @@ export class SettingField implements SettingTarget {
readonly componentMeta: ComponentMeta | null;
readonly designer: Designer;
readonly top: SettingTarget;
readonly transducer: Transducer;
get path() {
const path = this.parent.path.slice();
if (this.type === 'field') {
@ -139,6 +141,8 @@ export class SettingField implements SettingTarget {
if (this.type === 'group' && items) {
this.initItems(items);
}
this.transducer = new Transducer(this, { setter });
}
onEffect(action: () => void): () => void {
@ -272,6 +276,27 @@ export class SettingField implements SettingTarget {
purge() {
this.disposeItems();
}
// ======= compatibles ====
getHotValue(): any {
return this.transducer.toHot(this.getValue());
}
setHotValue(data: any) {
this.setValue(this.transducer.toNative(data));
}
getNode() {
return this.nodes[0];
}
getProps() {
return this.parent;
}
onValueChange() {
return () => {};
}
}
export function isSettingField(obj: any): obj is SettingField {

View File

@ -111,7 +111,7 @@ class SettingFieldView extends Component<{ field: SettingField }> {
forceInline: extraProps.forceInline,
key: field.id,
// === injection
prop: field, // for compatible
prop: field, // for compatible vision
field,
// === IO
value, // reaction point

View File

@ -1,36 +1,3 @@
:root {
--color-brand: #006cff;
--color-brand-light: #197aff;
--color-brand-dark: #0060e5;
--color-icon-normal: rgba(31, 56, 88, 0.4);
--color-icon-hover: rgba(31, 56, 88, 0.6);
--color-icon-active: #006cff;
--color-icon-reverse: #ffffff;
--color-line-light: rgba(31, 56, 88, 0.05);
--color-line-normal: rgba(31, 56, 88, 0.1);
--color-line-darken: rgba(18, 32, 50, 0.1);
--color-title: rgba(0, 0, 0, 0.8);
--color-text: rgba(0, 0, 0, 0.6);
--color-text-dark: rgba(0, 0, 0, 0.6);
--color-text-light: rgba(26, 26, 26, 0.6);
--color-text-reverse: rgba(255, 255, 255, 0.8);
--color-text-regular: rgba(31, 56, 88, 0.8);
--color-field-label: rgba(0, 0, 0, 0.4);
--color-field-text: rgba(0, 0, 0, 0.6);
--color-field-placeholder: rgba(31, 56, 88, 0.3);
--color-field-border: rgba(31, 56, 88, 0.3);
--color-field-border-hover: rgba(31, 56, 88, 0.4);
--color-field-border-active: rgba(31, 56, 88, 0.6);
--color-field-background: #ffffff;
--color-pane-background: #ffffff;
--color-block-background-normal: #ffffff;
--color-block-background-light: rgba(31, 56, 88, 0.03);
--color-block-background-shallow: rgba(31, 56, 88, 0.06);
--color-block-background-dark: rgba(31, 56, 88, 0.1);
--color-block-background-disabled: rgba(31, 56, 88, 0.2);
--color-block-background-deep-dark: #BAC3CC;
}
.lc-settings-main {
position: relative;
height: 100%;
@ -50,6 +17,13 @@
align-items: center;
padding-left: 5px;
border-bottom: 1px solid var(--color-line-normal);
.lc-settings-navigator-icon {
width: 16px;
height: 16px;
* {
fill: var(--color-icon-normal, rgba(31, 56, 88, 0.4));
}
}
.lc-settings-node-breadcrumb {
margin-left: 5px;
.next-breadcrumb {
@ -126,6 +100,8 @@
left: 0;
bottom: 0;
overflow-y: auto;
outline: none !important;
box-shadow: none !important;
}
}
.lc-outline-pane {

View File

@ -0,0 +1,41 @@
function getHotterFromSetter(setter) {
return setter && (setter.Hotter || (setter.type && setter.type.Hotter)) || []; // eslint-disable-line
}
function getTransducerFromSetter(setter) {
return setter && (
setter.transducer || setter.Transducer
|| (setter.type && (setter.type.transducer || setter.type.Transducer))
) || null; // eslint-disable-line
}
function combineTransducer(transducer, arr, context) {
if (!transducer && Array.isArray(arr)) {
const [toHot, toNative] = arr;
transducer = { toHot, toNative };
}
return {
toHot: (transducer && transducer.toHot || (x => x)).bind(context), // eslint-disable-line
toNative: (transducer && transducer.toNative || (x => x)).bind(context), // eslint-disable-line
};
}
export class Transducer {
constructor(context, config) {
this.setterTransducer = combineTransducer(
getTransducerFromSetter(config.setter),
getHotterFromSetter(config.setter),
context,
);
this.context = context;
}
toHot(data) {
return this.setterTransducer.toHot(data);
}
toNative(data) {
return this.setterTransducer.toNative(data);
}
}

View File

@ -1,4 +1,4 @@
import React, { PureComponent, createElement as reactCreateElement } from 'react';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Debug from 'debug';
import Div from '@ali/iceluna-comp-div';
@ -312,9 +312,9 @@ export default class BaseEngine extends PureComponent {
} else if (typeof idx === 'number' && !props.key) {
props.key = idx;
}
const createElement = engine.props.customCreateElement || reactCreateElement;
props.__id = schema.id;
const renderComp = (props) => {
return createElement(
return engine.createElement(
Comp,
props,
(!isFileSchema(schema) &&

View File

@ -1,4 +1,4 @@
import React, { PureComponent } from 'react';
import React, { PureComponent, createElement as reactCreateElement } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import Debug from 'debug';
@ -85,8 +85,12 @@ export default class Engine extends PureComponent {
}
};
createElement(Component, props, children) {
return (this.props.customCreateElement || reactCreateElement)(Component, props, children);
}
render() {
const { schema, designMode, appHelper, components } = this.props;
const { schema, designMode, appHelper, components, customCreateElement } = this.props;
if (isEmpty(schema)) {
return null;
}
@ -94,7 +98,7 @@ export default class Engine extends PureComponent {
return '模型结构异常';
}
debug('entry.render');
const allComponents = { ...ENGINE_COMPS, ...components };
const allComponents = { ...components, ...ENGINE_COMPS };
const Comp = allComponents[schema.componentName];
if (Comp) {
return (

View File

@ -3,6 +3,7 @@ import { ReactInstance, Fragment, Component, createElement } from 'react';
import { observer } from '@recore/obx-react';
import { SimulatorRenderer } from './renderer';
import './renderer.less';
import { host } from './host';
export default class SimulatorRendererView extends Component<{ renderer: SimulatorRenderer }> {
render() {
@ -50,8 +51,11 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> {
designMode={renderer.designMode}
suspended={renderer.suspended}
self={renderer.scope}
customCreateElement={(Component, props, children) => {
return createElement(Component, props, children);
customCreateElement={(Component: any, props: any, children: any) => {
const { __id, __desingMode, ...viewProps } = props;
viewProps.componentId = __id;
viewProps._leaf = host.document.getNode(__id);
return createElement(Component, viewProps, children);
}}
onCompGetRef={(schema: any, ref: ReactInstance | null) => {
renderer.mountInstance(schema.id, ref);

View File

@ -1,9 +1,9 @@
import { createElement, ReactInstance } from 'react';
import { createElement, ReactInstance, ComponentType } from 'react';
import { render as reactRender } from 'react-dom';
import { host } from './host';
import SimulatorRendererView from './renderer-view';
import { computed, obx } from '@recore/obx';
import { Asset } from '@ali/lowcode-globals';
import { Asset, isReactComponent } from '@ali/lowcode-globals';
import { getClientRects } from './utils/get-client-rects';
import loader from './utils/loader';
import { reactFindDOMNodes, FIBER_KEY } from './utils/react-find-dom-nodes';
@ -311,12 +311,17 @@ export interface LibraryMap {
[key: string]: string;
}
function buildComponents(libraryMap: LibraryMap, componentsMap: { [componentName: string]: NpmInfo }) {
function buildComponents(libraryMap: LibraryMap, componentsMap: { [componentName: string]: NpmInfo | ComponentType<any> }) {
const components: any = {};
Object.keys(componentsMap).forEach((componentName) => {
const component = findComponent(libraryMap, componentName, componentsMap[componentName]);
if (component) {
let component = componentsMap[componentName];
if (isReactComponent(component)) {
components[componentName] = component;
} else {
component = findComponent(libraryMap, componentName, component);
if (component) {
components[componentName] = component;
}
}
});
return components;

View File

@ -1,6 +1,8 @@
{
"entry": {
"index": "src/demo/index.ts"
"index": "src/demo/index.ts",
"react-simulator-renderer": "../react-simulator-renderer/src/index.ts",
"vision": "src/vision.ts"
},
"vendor": false,
"devServer": {
@ -12,7 +14,8 @@
"react-dom": "window.ReactDOM",
"prop-types": "window.PropTypes",
"@alifd/next": "window.Next",
"@ali/lowcode-globals": "window.LCEGlobals"
"@ali/visualengine": "window.VisualEngine",
"@ali/visualengine-utils": "window.VisualEngineUtils"
},
"plugins": [
[

View File

@ -21,6 +21,7 @@
"@ali/lowcode-plugin-undo-redo": "^0.8.6",
"@ali/lowcode-plugin-zh-en": "^0.8.8",
"@ali/lowcode-setters": "^0.8.8",
"@ali/ve-i18n-util": "^2.0.2",
"@ali/ve-icons": "^4.1.9",
"@ali/ve-less-variables": "2.0.3",
"@ali/ve-popups": "^4.2.5",

View File

@ -9,17 +9,67 @@
<script src="https://g.alicdn.com/code/lib/react/16.9.0/umd/react.development.js"></script>
<script src="https://g.alicdn.com/code/lib/react-dom/16.9.0/umd/react-dom.development.js"></script>
<script src="https://g.alicdn.com/code/lib/prop-types/15.7.2/prop-types.js"></script>
<script> React.PropTypes = PropTypes; </script>
<script>
React.PropTypes = PropTypes;
</script>
<script src="https://g.alicdn.com/platform/c/??react15-polyfill/0.0.1/dist/index.js,lodash/4.6.1/lodash.min.js,immutable/3.7.6/dist/immutable.min.js,natty-storage/2.0.2/dist/natty-storage.min.js,natty-fetch/2.6.0/dist/natty-fetch.pc.min.js,tinymce/4.2.5/tinymce-full.js"></script>
<script src="https://g.alicdn.com/mylib/moment/2.24.0/min/moment.min.js"></script>
<link rel="stylesheet" href="https://alifd.alicdn.com/npm/@alifd/next/1.11.6/next.min.css" />
<script src="https://unpkg.alibaba-inc.com/@alifd/next@1.18.17/dist/next.min.js"></script>
<script src="https://g.alicdn.com/vision/visualengine-utils/4.3.1/engine-utils.js"></script>
<!-- lowcode engine globals -->
<link rel="stylesheet" href="https://dev.g.alicdn.com/ali-lowcode/ali-lowcode-engine/0.9.0/globals.css" />
<link href="/css/vision.css" rel="stylesheet" />
<script>
window.pageConfig = {
env: 'release',
locale: 'zh_CN',
pageType: 'single',
deviceType: 'web',
appName: '基础包管理后台',
appType: 'legao_base_packages',
templateType: '',
pageId: 'FORM-3KYJN7RV-DIOD8LLK1WGQ89S7NHA92-QJVH497K-V',
slug: 'test',
appMode: 'back',
isAppAdmin: 'y',
isSuperAdmin: 'n',
isBetaDeveloper: 'n',
formType: 'display',
title: { en_US: '测试', type: 'i18n', zh_CN: '测试' },
urlPrefix: 'https://go.alibaba-inc.com',
APIUrlPrefix: 'https://go.alibaba-inc.com',
devVersion: '0.1.0', // 这个是子应用的变更 id
subAppType: '0.1.0',
appKey: 'legao_base_packages',
RE_VERSION: '7.1.1',
appSource: '',
isDomainDefault: 'n',
useReleaseBundle: 'n',
isDomainPkg: 'n',
medusaAppName: '',
domainCode: 'kS6SyH',
aecp: {
mdcDomain: '',
projectId: '',
appCode: '',
},
designerConfigs: {},
navConfig:
'{"appName":{"en_US":"基础包管理后台","key":"","type":"i18n","zh_CN":"基础包管理后台"},"bgColor":"white","data":[{"children":[],"hidden":false,"icon":"","inner":true,"navUuid":"FORM-3KYJN7RV-DIOD8LLK1WGQ89S7NHA92-QJVH497K-V","relateUuid":"FORM-3KYJN7RV-DIOD8LLK1WGQ89S7NHA92-QJVH497K-V","slug":"test","targetNew":false,"title":{"en_US":"测试","type":"i18n","zh_CN":"测试"}}],"isFixed":"y","isFold":"y","isFoldHorizontal":"n","languageChangeUrl":{"en_US":"/common/account/changeAccountLanguage.json","type":"i18n","zh_CN":"/common/account/changeAccountLanguage.json"},"layout":"auto","navStyle":"orange","navTheme":"light","openSubMode":false,"showAppTitle":true,"showCrumb":true,"showIcon":false,"showLanguageChange":true,"showNav":true,"showSearch":"n","singletons":{"FORM-3KYJN7RV-DIOD8LLK1WGQ89S7NHA92-QJVH497K-V":{"isFixed":"n","isFold":"n","isFoldHorizontal":"n","showAppTitle":false,"showCrumb":false,"showLanguageChange":false,"showNav":false,"showSearch":"n","singleton":false},"test":{"$ref":"$.singletons.FORM\\-3KYJN7RV\\-DIOD8LLK1WGQ89S7NHA92\\-QJVH497K\\-V"}},"type":"top_fold"}',
historyType: 'HASH',
isSinglePage: 'n',
rhino: 'n',
isMiniApp: '',
taskId: '',
appSchema: 'V5',
openSubMode: 'n',
};
window.g_config = {};
</script>
</head>
<body>
<!-- lowcode engine globals -->
<script src="https://dev.g.alicdn.com/ali-lowcode/ali-lowcode-engine/0.9.0/globals.js"></script>
<script src="/js/vision.js"></script>
<script src="https://g.alicdn.com/vision/visualengine-utils/4.3.1/engine-utils.js"></script>
</body>
</html>

View File

@ -148,7 +148,7 @@ class Prototype {
static overridePropsConfigure = overridePropsConfigure;
static create(config: OldPrototypeConfig | ComponentMetadata | ComponentMeta) {
return new Prototype(config);
};
}
private id: string;
private meta: ComponentMeta;
@ -241,6 +241,7 @@ class Prototype {
setPackageName(name: string) {
this.meta.setNpm({
package: name,
componentName: this.getComponentName(),
});
}
@ -256,7 +257,10 @@ class Prototype {
}
getView() {
return this.meta.getMetadata().experimental?.view || designer.currentDocument?.simulator?.getComponent(this.getComponentName());
return (
this.meta.getMetadata().experimental?.view ||
designer.currentDocument?.simulator?.getComponent(this.getComponentName())
);
}
}

View File

@ -143,11 +143,11 @@ export interface OldPrototypeConfig {
canSelecting?: boolean;
canContain?: (dragment: Node) => boolean; // => nestingRule
canDropTo?: ((container: Node) => boolean) | string | string[]; // => nestingRule
canDropto?: (container: Node) => boolean; // => nestingRule
canDropTo?: ((container: Node) => boolean) | boolean | string | string[]; // => nestingRule
canDropto?: ((container: Node) => boolean) | boolean | string | string[]; // => nestingRule
canDropIn?: ((dragment: Node) => boolean) | string | string[]; // => nestingRule
canDroping?: (dragment: Node) => boolean; // => nestingRule
canDropIn?: ((dragment: Node) => boolean) | boolean | string | string[]; // => nestingRule
canDroping?: ((dragment: Node) => boolean) | boolean | string | string[]; // => nestingRule
didDropOut?: (dragment: any, container: any) => void; // => hooks
didDropIn?: (dragment: any, container: any) => void; // => hooks
@ -387,9 +387,9 @@ export function upgradePropConfig(config: OldPropConfig) {
}
export function upgradeConfigure(items: OldPropConfig[]) {
const configure = [];
const configure: any[] = [];
let ignoreSlotName: any = null;
return items.forEach((config) => {
items.forEach((config) => {
if (config.slotName) {
ignoreSlotName = config.slotName;
} else if (ignoreSlotName) {
@ -401,6 +401,7 @@ export function upgradeConfigure(items: OldPropConfig[]) {
}
configure.push(upgradePropConfig(config));
});
return configure;
}
export function upgradeActions(actions?: Array<ComponentType<any> | ReactElement> | (() => ReactElement)) {
@ -507,11 +508,21 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
if (canContain) {
nestingRule.descendantWhitelist = canContain;
}
if (canDropTo || canDropto) {
nestingRule.parentWhitelist = canDropTo || canDropto;
if (canDropTo != null || canDropto != null) {
if (canDropTo === false || canDropto === false) {
nestingRule.parentWhitelist = () => false;
}
if (canDropTo !== true && canDropto !== true) {
nestingRule.parentWhitelist = canDropTo || canDropto;
}
}
if (canDropIn || canDroping) {
nestingRule.childWhitelist = canDropIn || canDroping;
if (canDropIn != null || canDroping != null) {
if (canDropIn === false || canDroping === false) {
nestingRule.childWhitelist = () => false;
}
if (canDropIn !== true && canDroping !== true) {
nestingRule.childWhitelist = canDropIn || canDroping;
}
}
component.nestingRule = nestingRule;

View File

@ -1,7 +1,9 @@
import Engine from '../vision'; // VisualEngine
import { editor } from '../editor';
// @ts-ignore
import Engine from '@ali/visualengine';
import loadUrls from './loader';
const { editor } = Engine;
Engine.init();
load();
@ -12,14 +14,7 @@ async function load() {
loadSchema();
}
const externals = [
'react',
'react-dom',
'prop-types',
'react-router',
'react-router-dom',
'@ali/recore',
];
const externals = ['react', 'react-dom', 'prop-types', 'react-router', 'react-router-dom', '@ali/recore'];
async function loadAssets() {
const assets = await editor.utils.get('./legao-assets.json');
// Trunk.setPackages(assets.packages);
@ -28,7 +23,7 @@ async function loadAssets() {
assets.packages.forEach((item: any) => {
if (item.package.indexOf('@ali/vc-') === 0 && item.urls) {
item.urls = item.urls.filter((url: string) => {
return url.indexOf('view.mobile') < 0
return url.indexOf('view.mobile') < 0;
});
} else if (item.package && externals.indexOf(item.package) > -1) {
item.urls = null;

View File

@ -4,7 +4,7 @@ import { Designer } from '@ali/lowcode-designer';
import { registerSetters } from '@ali/lowcode-setters';
import OutlinePane from '@ali/lowcode-plugin-outline-pane';
import SettingsPane from '@ali/lowcode-plugin-settings-pane';
import DesignerView from '@ali/lowcode-plugin-designer';
import DesignerPlugin from '@ali/lowcode-plugin-designer';
import { Skeleton } from './skeleton/skeleton';
registerSetters();
@ -21,7 +21,7 @@ editor.set(Designer, designer);
skeleton.mainArea.add({
name: 'designer',
type: 'Widget',
content: DesignerView,
content: DesignerPlugin,
});
skeleton.rightArea.add({
name: 'settingsPane',

View File

@ -0,0 +1,88 @@
import { EventEmitter } from 'events';
import { ALI_SCHEMA_VERSION } from './base/const';
interface ILiteralObject {
[key: string]: any;
}
export class Env {
public envs: ILiteralObject;
private emitter: EventEmitter;
private featureMap: ILiteralObject;
constructor() {
this.emitter = new EventEmitter();
this.emitter.setMaxListeners(0);
this.envs = {};
this.featureMap = {};
}
public get(name: string): any {
return this.getEnv(name);
}
public getEnv(name: string): any {
return this.envs[name];
}
public set(name: string, value: any) {
return this.setEnv(name, value);
}
public setEnv(name: string, value: any) {
const orig = this.envs[name];
if (JSON.stringify(orig) === JSON.stringify(value)) {
return;
}
this.envs[name] = value;
this.emitter.emit('envchange', this.envs, name, value);
}
public setEnvMap(envs: ILiteralObject): void {
this.envs = Object.assign(this.envs, envs);
this.emitter.emit('envchange', this.envs);
}
public getLocale(): string {
return this.getEnv('locale') || 'zh_CN';
}
public setLocale(locale: string) {
this.setEnv('locale', locale);
}
public setExpertMode(flag: string) {
this.setEnv('expertMode', !!flag);
}
public isExpertMode() {
return !!this.getEnv('expertMode');
}
public getSupportFeatures() {
return Object.assign({}, this.featureMap);
}
public setSupportFeatures(features: ILiteralObject) {
this.featureMap = Object.assign({}, this.featureMap, features);
}
public supports(name = 'supports') {
return !!this.featureMap[name];
}
public onEnvChange(func: (envs: ILiteralObject, name: string, value: any) => any) {
this.emitter.on('envchange', func);
return () => {
this.emitter.removeListener('envchange', func);
};
}
public getAliSchemaVersion() {
return ALI_SCHEMA_VERSION;
}
}
export default new Env();

View File

@ -0,0 +1,17 @@
import { Component } from "react";
export class Placeholder extends Component {
}
const Field = {
SettingField: Placeholder,
Stage: Placeholder,
PopupField: Placeholder,
EntryField: Placeholder,
AccordionField: Placeholder,
BlockField: Placeholder,
InlineField: Placeholder
};
export default Field;

View File

@ -0,0 +1,52 @@
import { designer } from './editor';
import { RootSchema } from '@ali/lowcode-globals';
import { DocumentModel } from '@ali/lowcode-designer';
const { project } = designer;
export interface OldPageData {
id: string;
layout: RootSchema;
[dataAddon: string]: any;
}
const pages = Object.assign(project, {
setPages(pages: OldPageData[]) {
project.load({
version: '1.0.0',
componentsMap: [],
componentsTree: pages.map(page => page.layout),
});
},
addPage(data: OldPageData) {
return project.open(data.layout);
},
getPage(fnOrIndex: ((page: DocumentModel) => boolean) | number) {
if (typeof fnOrIndex === 'number') {
return project.documents[fnOrIndex];
} else if (typeof fnOrIndex === 'function') {
return project.documents.find(fnOrIndex);
}
return null;
},
removePage(page: DocumentModel) {
page.remove();
},
getPages() {
return project.documents;
},
setCurrentPage(page: DocumentModel) {
page.active();
},
getCurrentPage() {
return project.currentDocument;
},
onPagesChange() {
// noop
},
onCurrentPageChange(fn: (page: DocumentModel) => void) {
return project.onCurrentDocumentChange(fn);
}
});
export default pages;

View File

@ -0,0 +1,616 @@
import { Component } from 'react';
import { EventEmitter } from 'events';
import { fromJS, Iterable, Map as IMMap } from 'immutable';
import logger from '@ali/vu-logger';
import { uniqueId, cloneDeep, isDataEqual, combineInitial, Transducer } from '@ali/ve-utils';
import I18nUtil from '@ali/ve-i18n-util';
import { getSetter } from '@ali/lowcode-globals';
import { editor } from './editor';
import { OldPropConfig, DISPLAY_TYPE } from './bundle/upgrade-metadata';
type IPropConfig = OldPropConfig;
// 1: chain -1: start 0: discard
const CHAIN_START = -1;
const CHAIN_HAS_REACH = 0;
export enum PROP_VALUE_CHANGED_TYPE {
/**
* normal set value
*/
SET_VALUE = 'SET_VALUE',
/**
* value changed caused by sub-prop value change
*/
SUB_VALUE_CHANGE = 'SUB_VALUE_CHANGE',
}
/**
* Dynamic setter will use 've.plugin.setterProvider' to
* calculate setter type in runtime
*/
let dynamicSetterProvider: any;
export interface IHotDataMap extends IMMap<string, any> {
value: any;
hotValue: any;
}
export interface ISetValueOptions {
disableMutator?: boolean;
type?: PROP_VALUE_CHANGED_TYPE;
}
export interface IVariableSettable {
useVariable?: boolean;
variableValue: string;
isUseVariable: () => boolean;
isSupportVariable: () => boolean;
setVariableValue: (value: string) => void;
setUseVariable: (flag?: boolean) => void;
getVariableValue: () => string;
onUseVariableChange: (func: (data: { isUseVariable: boolean }) => any) => void;
}
export default class Prop implements IVariableSettable {
/**
* Setters predefined as default options
* can by selected by user for every prop
*
* @static
* @memberof Prop
*/
public static INSET_SETTER = {};
public id: string;
public emitter: EventEmitter;
public inited: boolean;
public i18nLink: any;
public loopLock: boolean;
public props: any;
public parent: any;
public config: IPropConfig;
public initial: any;
public initialData: any;
public expanded: boolean;
public useVariable?: boolean;
/**
* value to be saved in schema it is usually JSON serialized
* prototype.js can config Transducer.toNative to generate value
*/
public value: any;
/**
* value to be used in VisualDesigner more flexible
* prototype.js can config Transducer.toHot to generate hotValue
*/
public hotValue: any;
/**
*
*/
public variableValue: string;
public hotData: IMMap<string, IHotDataMap>;
public defaultValue: any;
public transducer: any;
public inGroup: boolean;
constructor(parent: any, config: IPropConfig, data?: any) {
if (parent.isProps) {
this.props = parent;
this.parent = null;
} else {
this.props = parent.getProps();
this.parent = parent;
}
this.id = uniqueId(null as any, 'prop', 'engine-prop');
if (typeof config.setter === 'string') {
config.setter = getSetter(config.setter)?.component as any;
}
this.config = config;
this.emitter = new EventEmitter();
this.emitter.setMaxListeners(100);
this.initialData = data;
this.useVariable = false;
dynamicSetterProvider = editor.get('ve.plugin.setterProvider');
this.beforeInit();
}
public getId() {
return this.id;
}
public isTab() {
return this.getDisplay() === 'tab';
}
public isGroup() {
return false;
}
public beforeInit() {
if (IMMap.isMap(this.initialData)) {
this.value = this.initialData.get('value');
if (this.value && typeof this.value.toJS === 'function') {
this.value = this.value.toJS();
}
this.hotData = this.initialData;
} else {
this.value = this.initialData;
}
this.resolveValue();
let defaultValue = null;
if (this.config.defaultValue !== undefined) {
defaultValue = this.config.defaultValue;
} else if (typeof this.config.initialValue !== 'function') {
defaultValue = this.config.initialValue;
}
this.defaultValue = defaultValue;
this.transducer = new Transducer(this, this.config);
this.initial = combineInitial(this, this.config);
}
public resolveValue() {
if (this.value && this.value.type === 'variable') {
const { value, variable } = this.value;
this.value = value;
this.variableValue = variable;
this.useVariable = this.isSupportVariable();
} else {
this.useVariable = false;
}
}
public init(defaultValue?: any) {
if (this.inited) { return; }
this.value = this.initial(this.value,
this.defaultValue != null ? this.defaultValue : defaultValue);
if (this.hotData) {
const tempVal = this.hotData.get('value');
// if we create a prop from runtime data, we don't need initial() or set with defaultValue process
// but if we got an empty value, we fill with the initial() process and default value
if (Iterable.isIterable(tempVal)) {
this.value = tempVal.toJS() || this.value;
} else {
this.value = tempVal || this.value;
}
this.resolveValue();
}
this.i18nLink = I18nUtil.attach(this, this.value,
((val: any) => { this.setValue(val, false, true); }) as any);
// call config.accessor
const value = this.getValue();
if (this.hotData) {
this.hotValue = this.hotData.get('hotValue');
if (this.hotValue && Iterable.isIterable(this.hotValue)) {
this.hotValue = this.hotValue.toJS();
}
} else {
try {
this.hotValue = this.transducer.toHot(value);
} catch (e) {
logger.log('ERROR_PROP_VALUE');
logger.warn('属性初始化错误:', this);
}
this.hotData = fromJS({
hotValue: this.hotValue,
value: this.getMixValue(value),
});
}
this.inited = true;
}
public isInited() {
return this.inited;
}
public getHotData() {
return this.hotData;
}
public getProps() {
return this.props;
}
public getNode(): any {
return this.getProps().getNode();
}
/**
*
*
* @returns {string}
*/
public getName(): string {
const ns = this.parent ? `${this.parent.getName()}.` : '';
return ns + this.config.name;
}
public getKey() {
return this.config.name;
}
/**
*
*
* @returns {string}
*/
public getTitle() {
return this.config.title || this.getName();
}
public getTip() {
return this.config.tip || null;
}
public getValue(disableCache?: boolean, options?: {
disableAccessor?: boolean;
}) {
const accessor = this.config.accessor;
if (accessor && (!options || !options.disableAccessor)) {
const value = accessor.call(this, this.value);
if (!disableCache) {
this.value = value;
}
return value;
}
return this.value;
}
public getMixValue(value?: any) {
if (value == null) {
value = this.getValue();
}
if (this.isUseVariable()) {
value = {
type: 'variable',
value,
variable: this.getVariableValue(),
};
}
return value;
}
public toData() {
return cloneDeep(this.getMixValue());
}
public getDefaultValue() {
return this.defaultValue;
}
public getHotValue() {
return this.hotValue;
}
public getConfig<K extends keyof IPropConfig>(configName?: K): IPropConfig[K] | IPropConfig {
if (configName) {
return this.config[configName];
}
return this.config;
}
public sync() {
if (this.props.hasReach(this)) {
return;
}
const sync = this.config.sync;
if (sync) {
const value = sync.call(this, this.getValue(true));
if (value !== undefined) {
this.setValue(value);
}
} else {
// sync 的时候不再需要调用经过 accessor 处理之后的值了
// 这里之所以需要 setValue 是为了过 getValue() 中的 accessor 修饰函数
this.setValue(this.getValue(true), false, false, {
disableMutator: true,
});
}
}
public isUseVariable() {
return this.useVariable || false;
}
public isSupportVariable() {
return this.config.supportVariable || false;
}
public setVariableValue(value: string) {
if (!this.isUseVariable()) { return; }
const state = this.props.chainReach(this);
if (state === CHAIN_HAS_REACH) {
return;
}
this.variableValue = value;
if (this.modify()) {
this.valueChange();
this.props.syncPass(this);
}
if (state === CHAIN_START) {
this.props.endChain();
}
}
public setUseVariable(flag: boolean = false) {
if (this.useVariable === flag) { return; }
const state = this.props.chainReach(this);
if (state === CHAIN_HAS_REACH) {
return;
}
this.useVariable = flag;
this.expanded = true;
if (this.modify()) {
this.valueChange();
this.props.syncPass(this);
}
if (state === CHAIN_START) {
this.props.endChain();
}
this.emitter.emit('ve.prop.useVariableChange', { isUseVariable: flag });
if (this.config.useVariableChange) {
this.config.useVariableChange.call(this, { isUseVariable: flag });
}
}
public getVariableValue() {
return this.variableValue;
}
/**
* @param value
* @param isHotValue
* @param force
*/
public setValue(value: any, isHotValue?: boolean, force?: boolean, extraOptions?: ISetValueOptions) {
const state = this.props.chainReach(this);
if (state === CHAIN_HAS_REACH) {
return;
}
const preValue = this.value;
const preHotValue = this.hotValue;
if (isHotValue) {
this.hotValue = value;
this.value = this.transducer.toNative(this.hotValue);
} else {
if (!isDataEqual(value, this.value)) {
this.hotValue = this.transducer.toHot(value);
}
this.value = value;
}
this.i18nLink = I18nUtil.attach(this, this.value, ((val: any) => this.setValue(val, false, true)) as any);
const mutator = this.config.mutator;
if (!extraOptions) {
extraOptions = {};
}
if (mutator && !extraOptions.disableMutator) {
mutator.call(this, this.value);
}
if (this.modify(force)) {
this.valueChange(extraOptions);
this.props.syncPass(this);
}
if (state === CHAIN_START) {
this.props.endChain();
}
}
public setHotValue(hotValue: any, options?: ISetValueOptions) {
try {
this.setValue(hotValue, true, false, options);
} catch (e) {
logger.log('ERROR_PROP_VALUE');
logger.warn('属性值设置错误:', e, hotValue);
}
}
/**
*
* @param force
*/
public modify(force?: boolean) {
const hotData = this.hotData.merge(fromJS({
hotValue: this.getHotValue(),
value: this.getMixValue(),
}));
if (!force && hotData.equals(this.hotData)) {
return false;
}
this.hotData = hotData;
(this.parent || this.props).modify(this.getName());
return true;
}
public setHotData(hotData: IMMap<string, IHotDataMap>, options?: ISetValueOptions) {
if (!IMMap.isMap(hotData)) {
return;
}
this.hotData = hotData;
let value = hotData.get('value');
if (value && typeof value.toJS === 'function') {
value = value.toJS();
}
let hotValue = hotData.get('hotValue');
if (hotValue && typeof hotValue.toJS === 'function') {
hotValue = hotValue.toJS();
}
const preValue = value;
const preHotValue = hotValue;
this.value = value;
this.hotValue = hotValue;
this.resolveValue();
if (!options || !options.disableMutator) {
const mutator = this.config.mutator;
if (mutator) {
mutator.call(this, value);
}
}
this.valueChange();
}
public valueChange(options?: ISetValueOptions) {
if (this.loopLock) { return; }
this.emitter.emit('valuechange', options);
if (this.parent) {
this.parent.valueChange(options);
}
}
public getDisplay() {
return this.config.display || this.config.fieldStyle || 'block';
}
public isHidden() {
if (!this.isInited() || this.getDisplay() === DISPLAY_TYPE.NONE || this.isDisabled()) {
return true;
}
let hidden = this.config.hidden;
if (typeof hidden === 'function') {
hidden = hidden.call(this, this.getValue());
}
return hidden === true;
}
public isDisabled() {
let disabled = this.config.disabled;
if (typeof disabled === 'function') {
disabled = disabled.call(this, this.getValue());
}
return disabled === true;
}
public isIgnore() {
if (this.isDisabled()) { return true; }
let ignore = this.config.ignore;
if (typeof ignore === 'function') {
ignore = ignore.call(this, this.getValue());
}
return ignore === true;
}
public isExpand() {
if (this.expanded == null) {
this.expanded = !(this.config.collapsed || this.config.fieldCollapsed);
}
return this.expanded;
}
public toggleExpand() {
if (this.expanded) {
this.expanded = false;
} else {
this.expanded = true;
}
this.emitter.emit('expandchange', this.expanded);
}
public getSetter() {
if (dynamicSetterProvider) {
const setter = dynamicSetterProvider.call(this, this, this.getNode().getPrototype());
if (setter) {
return setter;
}
}
const setterConfig = this.config.setter;
if (typeof setterConfig === 'function' && !(setterConfig.prototype instanceof Component)) {
return (setterConfig as any).call(this, this.getValue());
}
if (Array.isArray(setterConfig)) {
let item;
for (item of setterConfig) {
if (item.condition?.call(this, this.getValue())) {
return item.setter;
}
}
return setterConfig[0].setter;
}
return setterConfig;
}
public getSetterData(): any {
if (Array.isArray(this.config.setter)) {
let item;
for (item of this.config.setter) {
if (item.condition?.call(this, this.getValue())) {
return item;
}
}
return this.config.setter[0];
}
return { };
}
public destroy() {
if (this.i18nLink) {
this.i18nLink.detach();
}
}
public onValueChange(func: () => any) {
this.emitter.on('valuechange', func);
return () => {
this.emitter.removeListener('valuechange', func);
};
}
public onExpandChange(func: () => any) {
this.emitter.on('expandchange', func);
return () => {
this.emitter.removeListener('expandchange', func);
};
}
public onUseVariableChange(func: (data: { isUseVariable: boolean }) => any) {
this.emitter.on('ve.prop.useVariableChange', func);
return () => {
this.emitter.removeListener('ve.prop.useVariableChange', func);
};
}
}

View File

@ -0,0 +1,47 @@
html.engine-cursor-move, html.engine-cursor-move * {
cursor: grabbing !important
}
html.engine-cursor-copy, html.engine-cursor-copy * {
cursor: copy !important
}
html.engine-cursor-ew-resize, html.engine-cursor-ew-resize * {
cursor: ew-resize !important
}
body, #engine {
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
box-sizing: border-box;
padding: 0;
margin: 0;
overflow: hidden;
text-rendering: optimizeLegibility;
-webkit-user-select: none;
-webkit-user-drag: none;
-webkit-text-size-adjust: none;
-webkit-touch-callout: none;
-webkit-font-smoothing: antialiased;
}
html {
min-width: 1024px;
}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.3);
border-radius: 5px;
}
html.engine-blur #engine {
-webkit-filter: blur(4px);
}

View File

@ -2,11 +2,12 @@ import * as utils from '@ali/ve-utils';
import Popup from '@ali/ve-popups';
import Icons from '@ali/ve-icons';
import { render } from 'react-dom';
import I18nUtil from '@ali/ve-i18n-util';
import { createElement } from 'react';
import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS } from './const';
import Bus from './bus';
import Symbols from './symbols';
import { skeleton } from './editor';
import { skeleton, editor } from './editor';
import { VisionWorkbench } from './skeleton/workbench';
import Panes from './panes';
import Exchange from './exchange';
@ -15,6 +16,11 @@ import VisualManager from './base/visualManager';
import Trunk from './bundle/trunk';
import Prototype from './bundle/prototype';
import Bundle from './bundle/bundle';
import Pages from './pages';
import Field from './field';
import Prop from './prop';
import Env from './env';
import './vision.less';
function init(container?: Element) {
if (!container) {
@ -31,7 +37,13 @@ function init(container?: Element) {
);
}
/**
* VE.ui.xxx
*
* Core UI Components
*/
const ui = {
Field,
Icon: Icons,
Icons,
Popup,
@ -39,11 +51,14 @@ const ui = {
const modules = {
VisualManager,
I18nUtil,
Prop,
};
const context = new VisualEngineContext();
const VisualEngine = {
editor,
/**
* VE.Popup
*/
@ -52,6 +67,8 @@ const VisualEngine = {
* VE Utils
*/
utils,
I18nUtil,
Env,
/* pub/sub 集线器 */
Bus,
/* 事件 */
@ -74,11 +91,13 @@ const VisualEngine = {
Trunk,
Prototype,
Bundle,
Pages,
};
export default VisualEngine;
(window as any).VisualEngine = VisualEngine;
/*
console.log(
`%cLowcodeEngine %cv${VERSION}`,