Merge branch 'preset-vision/0.9.0' of gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine into preset-vision/0.9.0

This commit is contained in:
mario.gk 2020-06-01 14:32:16 +08:00
commit 66d0bb06e9
27 changed files with 683 additions and 137 deletions

View File

@ -1,5 +1,6 @@
{
"entry": {
"index": "src/index",
"editor-preset-vision": "../editor-preset-vision/src/index.ts",
"react-simulator-renderer": "../react-simulator-renderer/src/index.ts"
},

View File

@ -4,7 +4,7 @@ import Viewport from './viewport';
import { createSimulator } from './create-simulator';
import { Node, ParentalNode, DocumentModel, isNode, contains, isRootNode } from '../document';
import ResourceConsumer from './resource-consumer';
import { AssetLevel, Asset, AssetList, assetBundle, assetItem, AssetType, isElement } from '@ali/lowcode-utils';
import { AssetLevel, Asset, AssetList, assetBundle, assetItem, AssetType, isElement, isFormEvent } from '@ali/lowcode-utils';
import {
DragObjectType,
isShaken,
@ -240,6 +240,8 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
doc.addEventListener(
'mousedown',
(downEvent: MouseEvent) => {
// fix for popups close logic
document.dispatchEvent(new Event('mousedown'));
if (this.liveEditing.editing) {
return;
}
@ -306,9 +308,14 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
doc.addEventListener(
'click',
(e) => {
// fix for popups close logic
document.dispatchEvent(new Event('click'));
const target = e.target as HTMLElement;
if (isFormEvent(e) || target?.closest('.next-input-group,.next-checkbox-group,.next-date-picker,.next-input,.next-month-picker,.next-number-picker,.next-radio-group,.next-range,.next-range-picker,.next-rating,.next-select,.next-switch,.next-time-picker,.next-upload,.next-year-picker,.next-breadcrumb-item,.next-calendar-header,.next-calendar-table')) {
e.preventDefault();
e.stopPropagation();
}
// stop response document click event
// e.preventDefault();
// e.stopPropagation();
// todo: catch link redirect
},
true,

View File

@ -140,9 +140,7 @@ export class LiveEditing {
// TODO: upward testing for b/i/a html elements
// 非文本编辑
// 国际化数据,改变当前
// JSExpression, 改变 mock 或 弹出绑定变量
}
get editing() {

View File

@ -249,6 +249,9 @@ export class ComponentMeta {
}
return true;
}
// compatiable vision
prototype?: any;
}
export function isComponentMeta(obj: any): obj is ComponentMeta {

View File

@ -394,7 +394,7 @@ export class Designer {
this._componentMetasMap.forEach((config, key) => {
const metaData = config.getMetadata();
if (metaData.devMode === 'lowcode') {
maps[key] = this.currentDocument?.simulator?.createComponent(metaData.schema);
maps[key] = this.currentDocument?.simulator?.createComponent(metaData.schema!);
} else {
const view = metaData.experimental?.view;
if (view) {

View File

@ -294,6 +294,9 @@ export class SettingPropEntry implements SettingEntry {
isUseVariable() {
return isJSExpression(this.getValue());
}
get useVariable() {
return this.isUseVariable();
}
getMockOrValue() {
const v = this.getValue();
if (isJSExpression(v)) {

View File

@ -2,7 +2,7 @@ import { EventEmitter } from 'events';
import { CustomView, isCustomView, IEditor } from '@ali/lowcode-types';
import { computed } from '@ali/lowcode-editor-core';
import { SettingEntry } from './setting-entry';
import { SettingField } from './setting-field';
import { SettingField, isSettingField } from './setting-field';
import { SettingPropEntry } from './setting-prop-entry';
import { Node } from '../../document';
import { ComponentMeta } from '../../component-meta';
@ -124,7 +124,14 @@ export class SettingTopEntry implements SettingEntry {
*
*/
get(propName: string | number): SettingPropEntry {
return new SettingPropEntry(this, propName);
const matched = this.items.find(item => {
if (isSettingField(item)) {
// TODO: thinkof use name or path?
return item.name === propName;
}
return false;
}) as SettingPropEntry;
return matched || (new SettingPropEntry(this, propName));
}
/**

View File

@ -1,4 +1,4 @@
import { obx, computed } from '@ali/lowcode-editor-core';
import { obx, computed, autorun } from '@ali/lowcode-editor-core';
import {
isDOMText,
isJSExpression,
@ -155,17 +155,29 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
this._children = new NodeChildren(this as ParentalNode, this.initialChildren(children));
this._children.interalInitParent();
this.props.import(this.transformProps(props || {}), extras);
this.setupAutoruns();
}
}
private transformProps(props: any): any {
// FIXME! support PropsList
const x = this.document.designer.transformProps(props, this, TransformStage.Init);
return x;
return this.document.designer.transformProps(props, this, TransformStage.Init);
// TODO: run transducers in metadata.experimental
}
private autoruns?: Array<() => void>;
private setupAutoruns() {
const autoruns = this.componentMeta.getMetadata().experimental?.autoruns;
if (!autoruns || autoruns.length < 1) {
return;
}
this.autoruns = autoruns.map(item => {
return autorun(() => {
item.autorun(this.props.get(item.name, true) as any)
}, true);
});
}
private initialChildren(children: any): NodeData[] {
// FIXME! this is dirty code
if (children == null) {
@ -591,6 +603,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
if (this.isParental()) {
this.children.purge();
}
this.autoruns?.forEach(dispose => dispose());
this.props.purge();
this.document.internalRemoveAndPurgeNode(this);
}
@ -608,7 +621,16 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
insertBefore(node: Node, ref?: Node) {
this.children?.insert(node, ref ? ref.index : null);
}
insertAfter(node: Node, ref?: Node) {
insertAfter(node: any, ref?: Node) {
if (!isNode(node)) {
if (node.getComponentName) {
node = this.document.createNode({
componentName: node.getComponentName(),
});
} else {
node = this.document.createNode(node);
}
}
this.children?.insert(node, ref ? ref.index + 1 : null);
}
getParent() {
@ -687,6 +709,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
* @deprecated
*/
getSuitablePlace(node: Node, ref: any): any {
// TODO:
if (this.isRoot()) {
return { container: this, ref };
}
@ -721,7 +744,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
}
getPrototype() {
return this;
return this.componentMeta.prototype;
}
getIcon() {

View File

@ -1,6 +1,7 @@
import { obx, autorun, untracked, computed } from '@ali/lowcode-editor-core';
import { Prop, IPropParent, UNSET } from './prop';
import { Props } from './props';
import { Node } from '../node';
export type PendingItem = Prop[];
export class PropStash implements IPropParent {
@ -15,8 +16,10 @@ export class PropStash implements IPropParent {
return maps;
}
private willPurge: () => void;
readonly owner: Node;
constructor(readonly props: Props, write: (item: Prop) => void) {
this.owner = props.owner;
this.willPurge = autorun(() => {
if (this.space.size < 1) {
return;

View File

@ -4,7 +4,7 @@ import { uniqueId, isPlainObject, hasOwnProperty } from '@ali/lowcode-utils';
import { PropStash } from './prop-stash';
import { valueToSource } from './value-to-source';
import { Props } from './props';
import { SlotNode } from '../node';
import { SlotNode, Node } from '../node';
import { TransformStage } from '../transform-stage';
export const UNSET = Symbol.for('unset');
@ -13,12 +13,14 @@ export type UNSET = typeof UNSET;
export interface IPropParent {
delete(prop: Prop): void;
readonly props: Props;
readonly owner: Node;
}
export type ValueTypes = 'unset' | 'literal' | 'map' | 'list' | 'expression' | 'slot';
export class Prop implements IPropParent {
readonly isProp = true;
readonly owner: Node;
/**
* @see SettingTarget
@ -350,6 +352,7 @@ export class Prop implements IPropParent {
key?: string | number,
spread = false,
) {
this.owner = parent.owner;
this.props = parent.props;
if (value !== UNSET) {
this.setValue(value);
@ -613,6 +616,10 @@ export class Prop implements IPropParent {
getProps() {
return this.parent;
}
getNode() {
return this.owner;
}
}
export function isProp(obj: any): obj is Prop {

View File

@ -19,7 +19,6 @@
"@ali/lowcode-editor-skeleton": "^0.8.24",
"@ali/lowcode-plugin-designer": "^0.9.18",
"@ali/lowcode-plugin-outline-pane": "^0.8.24",
"@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

@ -1,5 +1,5 @@
import { ComponentType, ReactElement } from 'react';
import { ComponentMetadata, FieldConfig, InitialItem, FilterItem } from '@ali/lowcode-types';
import { ComponentMetadata, FieldConfig, InitialItem, FilterItem, AutorunItem } from '@ali/lowcode-types';
import {
ComponentMeta,
addBuiltinComponentAction,
@ -22,12 +22,14 @@ const GlobalPropsConfigure: Array<{
position: string;
initials?: InitialItem[];
filters?: FilterItem[];
autoruns?: AutorunItem[];
config: FieldConfig
}> = [];
const Overrides: {
[componentName: string]: {
initials?: InitialItem[];
filters?: FilterItem[];
autoruns?: AutorunItem[];
override: any;
};
} = {};
@ -35,10 +37,12 @@ const Overrides: {
function addGlobalPropsConfigure(config: OldGlobalPropConfig) {
const initials: InitialItem[] = [];
const filters: FilterItem[] = [];
const autoruns: AutorunItem[] = [];
GlobalPropsConfigure.push({
position: config.position || 'bottom',
initials,
filters,
autoruns,
config: upgradePropConfig(config, {
addInitial: (item) => {
initials.push(item);
@ -46,6 +50,9 @@ function addGlobalPropsConfigure(config: OldGlobalPropConfig) {
addFilter: (item) => {
filters.push(item);
},
addAutorun: (item) => {
autoruns.push(item);
},
})
});
}
@ -60,24 +67,29 @@ function removeGlobalPropsConfigure(name: string) {
function overridePropsConfigure(componentName: string, config: { [name: string]: OldPropConfig } | OldPropConfig[]) {
const initials: InitialItem[] = [];
const filters: FilterItem[] = [];
const autoruns: AutorunItem[] = [];
const addInitial = (item: InitialItem) => {
initials.push(item);
};
const addFilter = (item: FilterItem) => {
filters.push(item);
};
const addAutorun = (item: AutorunItem) => {
autoruns.push(item);
};
let override: any;
if (Array.isArray(config)) {
override = upgradeConfigure(config, { addInitial, addFilter });
override = upgradeConfigure(config, { addInitial, addFilter, addAutorun });
} else {
override = {};
Object.keys(config).forEach(key => {
override[key] = upgradePropConfig(config[key], { addInitial, addFilter });
override[key] = upgradePropConfig(config[key], { addInitial, addFilter, addAutorun });
});
}
Overrides[componentName] = {
initials,
filters,
autoruns,
override,
};
}
@ -107,6 +119,7 @@ registerMetadataTransducer(
} else if (position === 'bottom') {
bottom.push(item.config);
}
// TODO: replace autoruns,initials,filters
});
const override = Overrides[componentName]?.override;
@ -127,6 +140,7 @@ registerMetadataTransducer(
}
}
}
// TODO: replace autoruns,initials,filters
}
return metadata;
@ -202,7 +216,7 @@ class Prototype {
return new Prototype(config);
}
private id: string;
readonly isPrototype = true;
private meta: ComponentMeta;
readonly options: OldPrototypeConfig | ComponentMetadata;
@ -215,11 +229,11 @@ class Prototype {
const metadata = isNewSpec(input) ? input : upgradeMetadata(input);
this.meta = designer.createComponentMeta(metadata);
}
this.id = uniqueId('prototype');
(this.meta as any).prototype = this;
}
getId() {
return this.id;
return this.getComponentName();
}
getConfig(configName?: keyof (OldPrototypeConfig | ComponentMetadata)) {
@ -316,4 +330,8 @@ class Prototype {
}
}
export function isPrototype(obj: any): obj is Prototype {
return obj && obj.isPrototype;
}
export default Prototype;

View File

@ -41,6 +41,10 @@ export class Trunk {
return this.metaBundle.getFromMeta(name);
}
getPrototypeById(id: string) {
return this.getPrototype(id);
}
listByCategory() {
const categories: any[] = [];
const categoryMap: any = {};

View File

@ -1,6 +1,7 @@
import { ComponentType, ReactElement, isValidElement, ComponentClass } from 'react';
import { isPlainObject } from '@ali/lowcode-utils';
import { isI18nData, SettingTarget, InitialItem, FilterItem, isJSSlot, ProjectSchema } from '@ali/lowcode-types';
import { isI18nData, SettingTarget, InitialItem, FilterItem, isJSSlot, ProjectSchema, AutorunItem } from '@ali/lowcode-types';
import { untracked } from '@ali/lowcode-editor-core';
type Field = SettingTarget;
@ -292,19 +293,44 @@ export function upgradePropConfig(config: OldPropConfig, collector: ConfigCollec
};
}
if (accessor && !slotName) {
extraProps.getValue = (field: Field, fieldValue: any) => {
return accessor.call(field, fieldValue);
};
if (!initialFn) {
initialFn = accessor;
if (!slotName) {
if (accessor) {
extraProps.getValue = (field: Field, fieldValue: any) => {
return accessor.call(field, fieldValue);
};
}
if (sync || accessor) {
collector.addAutorun({
name,
autorun: (field: Field) => {
let fieldValue = untracked(() => field.getValue());
if (accessor) {
fieldValue = accessor.call(field, fieldValue)
}
if (sync) {
fieldValue = sync.call(field, fieldValue);
if (fieldValue !== undefined) {
field.setValue(fieldValue);
}
} else {
field.setValue(fieldValue);
}
}
});
}
if (mutator) {
extraProps.setValue = (field: Field, value: any) => {
mutator.call(field, value, value);
};
}
}
const setterInitial = getInitialFromSetter(setter);
collector.addInitial({
// FIXME! name should be "xxx.xxx"
// FIXME! name could be "xxx.xxx"
name: slotName || name,
initial: (field: Field, currentValue: any) => {
// FIXME! read from prototype.defaultProps
@ -347,20 +373,6 @@ export function upgradePropConfig(config: OldPropConfig, collector: ConfigCollec
});
}
if (sync) {
extraProps.autorun = (field: Field) => {
const value = sync.call(field, field.getValue());
if (value !== undefined) {
field.setValue(value);
}
};
}
if (mutator && !slotName) {
extraProps.setValue = (field: Field, value: any) => {
mutator.call(field, value, value);
};
}
if (slotName) {
newConfig.name = slotName;
if (!newConfig.title && slotTitle) {
@ -398,7 +410,6 @@ export function upgradePropConfig(config: OldPropConfig, collector: ConfigCollec
let primarySetter: any;
if (type === 'composite') {
const initials: InitialItem[] = [];
const filters: FilterItem[] = [];
const objItems = items
? upgradeConfigure(items,
{
@ -406,8 +417,17 @@ export function upgradePropConfig(config: OldPropConfig, collector: ConfigCollec
initials.push(item);
},
addFilter: (item) => {
filters.push(item);
}
collector.addFilter({
name: `${name}.${item.name}`,
filter: item.filter,
});
},
addAutorun: (item) => {
collector.addAutorun({
name: `${name}.${item.name}`,
autorun: item.autorun,
});
},
}
)
: [];
@ -483,10 +503,12 @@ export function upgradePropConfig(config: OldPropConfig, collector: ConfigCollec
type AddInitial = (initialItem: InitialItem) => void;
type AddFilter = (filterItem: FilterItem) => void;
type AddAutorun = (autorunItem: AutorunItem) => void;
type ConfigCollector = {
addInitial: AddInitial,
addFilter: AddFilter,
addInitial: AddInitial;
addFilter: AddFilter;
addAutorun: AddAutorun;
}
function getInitialFromSetter(setter: any) {
@ -755,6 +777,7 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
const initials: InitialItem[] = [];
const filters: FilterItem[] = [];
const autoruns: AutorunItem[] = [];
const props = upgradeConfigure(configure || [],
{
addInitial: (item) => {
@ -763,10 +786,14 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
addFilter: (item) => {
filters.push(item);
},
addAutorun: (item) => {
autoruns.push(item);
}
}
);
experimental.initials = initials;
experimental.filters = filters;
experimental.autoruns = autoruns;
const supports: any = {};
if (canUseCondition != null) {

View File

@ -1,21 +1,18 @@
import Env from './env';
import { isJSSlot, isI18nData, isJSExpression } from '@ali/lowcode-types';
import { isPlainObject } from '@ali/lowcode-utils';
const I18nUtil = require('@ali/ve-i18n-util');
import i18nUtil from './i18n-util';
interface I18nObject {
type?: string;
use?: string;
key?: string;
[lang: string]: string | undefined;
}
export function i18nReducer(obj?: any): any {
// FIXME: 表达式使用 mock 值未来live 模式直接使用原始值
export function deepValueParser(obj?: any): any {
if (isJSExpression(obj)) {
obj = obj.mock;
}
if (!obj) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => i18nReducer(item));
return obj.map((item) => deepValueParser(item));
}
if (isPlainObject(obj)) {
if (isI18nData(obj)) {
@ -23,19 +20,20 @@ export function i18nReducer(obj?: any): any {
let locale = Env.getLocale();
if (obj.key) {
// FIXME: 此处需要升级I18nUtil改成响应式
return I18nUtil.get(obj.key, locale);
return i18nUtil.get(obj.key, locale);
}
if (locale !== 'zh_CN' && locale !== 'zh_TW' && !obj[locale]) {
locale = 'en_US';
}
return obj[obj.use || locale] || obj.zh_CN;
}
if (isJSSlot(obj) || isJSExpression(obj)) {
if (isJSSlot(obj)) {
return obj;
}
const out: I18nObject = {};
const out: any = {};
Object.keys(obj).forEach((key) => {
out[key] = i18nReducer(obj[key]);
out[key] = deepValueParser(obj[key]);
});
return out;
}

View File

@ -1,5 +1,6 @@
import { designer } from './editor';
import { DragObjectType, isNode, isDragNodeDataObject } from '@ali/lowcode-designer';
import { isPrototype } from './bundle/prototype';
const dragon = designer.dragon;
const DragEngine = {
@ -9,7 +10,14 @@ const DragEngine = {
if (!r) {
return null;
}
if (isNode(r)) {
if (isPrototype(r)) {
return {
type: DragObjectType.NodeData,
data: {
componentName: r.getComponentName(),
},
};
} else if (isNode(r)) {
return {
type: DragObjectType.Node,
nodes: [r],

View File

@ -1,7 +1,7 @@
import { isJSBlock, isJSExpression, isJSSlot, isI18nData } from '@ali/lowcode-types';
import { isPlainObject, hasOwnProperty } from '@ali/lowcode-utils';
import { globalContext, Editor } from '@ali/lowcode-editor-core';
import { Designer, LiveEditing, TransformStage, addBuiltinComponentAction, Node } from '@ali/lowcode-designer';
import { Designer, LiveEditing, TransformStage, Node } from '@ali/lowcode-designer';
import Outline, { OutlineBackupPane, getTreeMaster } from '@ali/lowcode-plugin-outline-pane';
import { toCss } from '@ali/vu-css-style';
import logger from '@ali/vu-logger';
@ -9,9 +9,8 @@ import logger from '@ali/vu-logger';
import DesignerPlugin from '@ali/lowcode-plugin-designer';
import { Skeleton, SettingsPrimaryPane } from '@ali/lowcode-editor-skeleton';
import { i18nReducer } from './i18n-reducer';
import { InstanceNodeSelector } from './components';
import { liveEditingRule } from './vc-live-editing';
import { deepValueParser } from './deep-value-parser';
import { liveEditingRule, liveEditingSaveHander } from './vc-live-editing';
export const editor = new Editor();
globalContext.register(editor, Editor);
@ -49,8 +48,6 @@ designer.addPropsReducer((props, node) => {
return props;
}, TransformStage.Init);
// 国际化渲染时处理
designer.addPropsReducer(i18nReducer, TransformStage.Render);
function filterReducer(props: any, node: Node): any {
const filters = node.componentMeta.getMetadata().experimental?.filters;
@ -152,31 +149,8 @@ function appendStyleNode(props: any, styleProp: any, cssClass: string, cssId: st
}
designer.addPropsReducer(stylePropsReducer, TransformStage.Render);
// FIXME: 表达式使用 mock 值未来live 模式直接使用原始值
function expressionReducer(obj?: any): any {
// TODO: merge with i18nReducer for optimize
if (!obj) {
return obj;
}
if (Array.isArray(obj)) {
return obj.map((item) => expressionReducer(item));
}
if (isPlainObject(obj)) {
if (isJSExpression(obj)) {
return obj.mock;
}
if (isJSSlot(obj) || isI18nData(obj)) {
return obj;
}
const out: any = {};
Object.keys(obj).forEach((key) => {
out[key] = expressionReducer(obj[key]);
});
return out;
}
return obj;
}
designer.addPropsReducer(expressionReducer, TransformStage.Render);
// 国际化 & Expression 渲染时处理
designer.addPropsReducer(deepValueParser, TransformStage.Render);
skeleton.add({
area: 'mainArea',
@ -212,11 +186,4 @@ skeleton.add({
});
LiveEditing.addLiveEditingSpecificRule(liveEditingRule);
// 实例节点选择器,线框高亮
// addBuiltinComponentAction({
// name: 'instance-node-selector',
// content: InstanceNodeSelector,
// important: true,
// condition: 'always'
// });
LiveEditing.addLiveEditingSaveHandler(liveEditingSaveHander);

View File

@ -0,0 +1,79 @@
declare enum LANGUAGES {
zh_CN = 'zh_CN',
en_US = 'en_US'
}
export interface I18nRecord {
type?: 'i18n';
[key: string]: string;
/**
* i18n unique key
*/
key?: string;
}
export interface I18nRecordData {
gmtCreate: Date;
gmtModified: Date;
i18nKey: string;
i18nText: I18nRecord;
id: number;
}
export interface II18nUtilConfigs {
items?: {};
/**
*
*/
disableInstantLoad?: boolean;
/**
*
*/
disableFullLoad?: boolean;
loader?: (configs: ILoaderConfigs) => Promise<I18nRecordData[]>;
remover?: (key: string, dic: I18nRecord) => Promise<void>;
saver?: (key: string, dic: I18nRecord) => Promise<void>;
}
export interface ILoaderConfigs {
/**
* search keywords
*/
keyword?: string;
/**
* should load all i18n items
*/
isFull?: boolean;
/**
* search i18n item based on uniqueKey
*/
key?: string;
}
export interface II18nUtil {
init(config: II18nUtilConfigs): void;
isInitialized(): boolean;
isReady(): boolean;
attach(prop: object, value: I18nRecord, updator: () => any);
search(keyword: string, silent?: boolean);
load(configs: ILoaderConfigs): Promise<I18nRecord[]>;
/**
* Get local i18n Record
* @param key
* @param lang
*/
get(key: string, lang: string): string | I18nRecord;
getFromRemote(key: string): Promise<I18nRecord>;
getItem(key: string, forceData?: boolean): any;
getItems(): I18nRecord[];
update(key: string, doc: I18nRecord, lang: LANGUAGES);
create(doc: I18nRecord, lang: LANGUAGES): string;
remove(key: string): Promise<void>;
onReady(func: () => any);
onRowsChange(func: () => any);
onChange(func: (dic: I18nRecord) => any);
}
declare const i18nUtil: II18nUtil;
export default i18nUtil;

View File

@ -0,0 +1,310 @@
import { EventEmitter } from 'events';
import { obx } from '@ali/lowcode-editor-core';
let keybase = Date.now();
function keygen(maps) {
let key;
do {
key = `i18n-${(keybase).toString(36)}`;
keybase += 1;
} while (key in maps);
return key;
}
class DocItem {
constructor(parent, doc, unInitial) {
this.parent = parent;
const { use, ...strings } = doc;
this.doc = obx.val({
type: 'i18n',
...strings,
});
this.emitter = new EventEmitter;
this.inited = unInitial !== true;
}
getKey() {
return this.doc.key;
}
getDoc(lang) {
if (lang) {
return this.doc[lang];
}
return this.doc;
}
setDoc(doc, lang, initial) {
if (lang) {
this.doc[lang] = doc;
} else {
const { use, strings } = doc || {};
Object.assign(this.doc, strings);
}
this.emitter.emit('change', this.doc);
if (initial) {
this.inited = true;
} else if (this.inited) {
this.parent._saveChange(this.doc.key, this.doc);
}
}
remove() {
if (!this.inited) return Promise.reject('not initialized');
const { key, ...doc } = this.doc; // eslint-disable-line
this.emitter.emit('change', doc);
return this.parent.remove(this.getKey());
}
onChange(func) {
this.emitter.on('change', func);
return () => {
this.emitter.removeListener('change', func);
};
}
}
class I18nUtil {
constructor() {
this.emitter = new EventEmitter;
// original data source from remote
this.i18nData = {};
// current i18n records on the left pane
this.items = [];
this.maps = {};
// full list of i18n records for synchronized call
this.fullList = [];
this.fullMap = {};
this.config = {};
this.ready = false;
this.isInited = false;
}
_prepareItems(items, isFull = false, isSilent = false) {
this[isFull ? 'fullList' : 'items'] = items.map((dict) => {
let item = this[isFull ? 'fullMap' : 'maps'][dict.key];
if (item) {
item.setDoc(dict, null, true);
} else {
item = new DocItem(this, dict);
this[isFull ? 'fullMap' : 'maps'][dict.key] = item;
}
return item;
});
if (this.ready && !isSilent) {
this.emitter.emit('rowschange');
this.emitter.emit('change');
} else {
this.ready = true;
this.emitter.emit('ready');
}
}
_load(configs = {}, silent) {
if (!this.config.loader) {
console.error(new Error('Please load loader while init I18nUtil.'));
return Promise.reject();
}
return this.config.loader(configs).then((data) => {
if (configs.i18nKey) {
return Promise.resolve(data.i18nText);
}
this._prepareItems(data.data, configs.isFull, silent);
// set pagination data to i18nData
this.i18nData = data;
if (!silent) {
this.emitter.emit('rowschange');
this.emitter.emit('change');
}
return Promise.resolve(this.items.map(i => i.getDoc()));
});
}
_saveToItems(key, dict) {
let item = null;
item = this.items.find(doc => doc.getKey() === key);
if (!item) {
item = this.fullList.find(doc => doc.getKey() === key);
}
if (item) {
item.setDoc(dict);
} else {
item = new DocItem(this, {
key,
...dict,
});
this.items.unshift(item);
this.fullList.unshift(item);
this.maps[key] = item;
this.fullMap[key] = item;
this._saveChange(key, dict, true);
}
}
_saveChange(key, dict, rowschange) {
if (rowschange) {
this.emitter.emit('rowschange');
}
this.emitter.emit('change');
if (dict === null) {
delete this.maps[key];
delete this.fullMap[key];
}
return this._save(key, dict);
}
_save(key, dict) {
const saver = dict === null ? this.config.remover : this.config.saver;
if (!saver) return Promise.reject('Saver function is not set');
return saver(key, dict);
}
init(config) {
if (this.isInited) return;
this.config = config || {};
if (this.config.items) {
// inject to current page
this._prepareItems(this.config.items);
}
if (!this.config.disableInstantLoad) {
this._load({ isFull: !this.config.disableFullLoad });
}
this.isInited = true;
}
isInitialized() {
return this.isInited;
}
isReady() {
return this.ready;
}
// add events updater when i18n record change
// we should notify engine's view to change
attach(prop, value, updator) {
const isI18nValue = value && value.type === 'i18n' && value.key;
const key = isI18nValue ? value.key : null;
if (prop.i18nLink) {
if (isI18nValue && (key === prop.i18nLink.key)) {
return prop.i18nLink;
}
prop.i18nLink.detach();
}
if (isI18nValue) {
return {
key,
detach: this.getItem(key, value).onChange(updator),
};
}
return null;
}
/**
* 搜索 i18n 词条
*
* @param {any} keyword 搜索关键字
* @param {boolean} [silent=false] 是否刷新左侧的 i18n 数据
* @returns
*
* @memberof I18nUtil
*/
search(keyword, silent = false) {
return this._load({ keyword }, silent);
}
load(configs = {}) {
return this._load(configs);
}
get(key, lang) {
const item = this.getItem(key);
if (item) {
return item.getDoc(lang);
}
return null;
}
getFromRemote(key) {
return this._load({ i18nKey: key });
}
getItem(key, forceData) {
if (forceData && !this.maps[key] && !this.fullList[key]) {
const item = new DocItem(this, {
key,
...forceData,
}, true);
this.maps[key] = item;
this.fullMap[key] = item;
this.fullList.push(item);
this.items.push(item);
}
return this.maps[key] || this.fullMap[key];
}
getItems() {
return this.items;
}
update(key, doc, lang) {
let dict = this.get(key) || {};
if (!lang) {
dict = doc;
} else {
dict[lang] = doc;
}
this._saveToItems(key, dict);
}
create(doc, lang) {
const dict = lang ? { [lang]: doc } : doc;
const key = keygen(this.fullMap);
this._saveToItems(key, dict);
return key;
}
remove(key) {
const index = this.items.findIndex(item => item.getKey() === key);
const indexG = this.fullList.findIndex(item => item.getKey() === key);
if (index > -1) {
this.items.splice(index, 1);
}
if (indexG > -1) {
this.fullList.splice(index, 1);
}
return this._saveChange(key, null, true);
}
onReady(func) {
this.emitter.on('ready', func);
return () => {
this.emitter.removeListener('ready', func);
};
}
onRowsChange(func) {
this.emitter.on('rowschange', func);
return () => {
this.emitter.removeListener('rowschange', func);
};
}
onChange(func) {
this.emitter.on('change', func);
return () => {
this.emitter.removeListener('change', func);
};
}
}
export default new I18nUtil();

View File

@ -3,7 +3,7 @@ import Popup from '@ali/ve-popups';
import Icons from '@ali/ve-icons';
import logger from '@ali/vu-logger';
import { render } from 'react-dom';
import I18nUtil from '@ali/ve-i18n-util';
import I18nUtil from './i18n-util';
import { hotkey as Hotkey } from '@ali/lowcode-editor-core';
import { createElement } from 'react';
import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS, VERSION as Version } from './base/const';
@ -164,7 +164,7 @@ export {
const version = '6.0.0(LowcodeEngine 0.9.0-beta)';
console.log(
`%cVisionEngine %cv${version}`,
"color:#000;font-weight:bold;",
"color:green;font-weight:bold;"
`%c VisionEngine %c v${version} `,
"padding: 2px 1px; border-radius: 3px 0 0 3px; color: #fff; background: #606060;font-weight:bold;",
"padding: 2px 1px; border-radius: 0 3px 3px 0; color: #fff; background: #42c02e;font-weight:bold;"
);

View File

@ -4,34 +4,60 @@ import { DocumentModel } from '@ali/lowcode-designer';
const { project } = designer;
export interface OldPageData {
export interface PageDataV1 {
id: string;
componentsTree: RootSchema[];
layout: RootSchema;
[dataAddon: string]: any;
}
export interface PageDataV2 {
id: string;
componentsTree: RootSchema[];
[dataAddon: string]: any;
}
function isPageDataV1(obj: any): obj is PageDataV1 {
return obj && obj.layout;
}
function isPageDataV2(obj: any): obj is PageDataV2 {
return obj && obj.componentsTree && Array.isArray(obj.componentsTree);
}
type OldPageData = PageDataV1 | PageDataV2;
const pages = Object.assign(project, {
setPages(pages: OldPageData[]) {
if (!pages || !Array.isArray(pages) || pages.length === 0) {
throw new Error('pages schema 不合法');
}
if (pages[0].componentsTree[0]) {
pages[0].componentsTree[0].componentName = 'Page';
// FIXME
pages[0].componentsTree[0].lifeCycles = {};
pages[0].componentsTree[0].methods = {};
let componentsTree: any;
if (isPageDataV1(pages[0])) {
componentsTree = [pages[0].layout];
} else {
componentsTree = pages[0].componentsTree;
if (componentsTree[0]) {
componentsTree[0].componentName = 'Page';
// FIXME
componentsTree[0].lifeCycles = {};
componentsTree[0].methods = {};
}
}
project.load({
version: '1.0.0',
componentsMap: [],
componentsTree: pages[0].componentsTree,
componentsTree,
}, true);
},
// FIXME:
addPage(data: OldPageData) {
return project.open(data.layout);
addPage(data: OldPageData | RootSchema) {
if (isPageDataV1(data)) {
data = data.layout;
} else if (isPageDataV2(data)) {
data = data.componentsTree[0];
}
return project.open(data);
},
getPage(fnOrIndex: ((page: DocumentModel) => boolean) | number) {
if (typeof fnOrIndex === 'number') {

View File

@ -1,7 +1,7 @@
import { EditingTarget, Node as DocNode } from '@ali/lowcode-designer';
import { EditingTarget, Node as DocNode, SaveHandler } from '@ali/lowcode-designer';
import Env from './env';
import { isJSExpression } from '@ali/lowcode-types';
const I18nUtil = require('@ali/ve-i18n-util');
import { isJSExpression, isI18nData } from '@ali/lowcode-types';
import i18nUtil from './i18n-util';
interface I18nObject {
type?: string;
@ -13,7 +13,7 @@ interface I18nObject {
function getI18nText(obj: I18nObject) {
let locale = Env.getLocale();
if (obj.key) {
return I18nUtil.get(obj.key, locale);
return i18nUtil.get(obj.key, locale);
}
if (locale !== 'zh_CN' && locale !== 'zh_TW' && !obj[locale]) {
locale = 'en_US';
@ -26,7 +26,10 @@ function getText(node: DocNode, prop: string) {
if (!p || p.isUnset()) {
return null;
}
const v = p.getValue();
let v = p.getValue();
if (isJSExpression(v)) {
v = v.mock;
}
if (v == null) {
return null;
}
@ -36,9 +39,7 @@ function getText(node: DocNode, prop: string) {
if ((v as any).type === 'i18n') {
return getI18nText(v as any);
}
if (isJSExpression(v)) {
return v.mock;
}
return Symbol.for('not-literal');
}
export function liveEditingRule(target: EditingTarget) {
@ -73,7 +74,43 @@ function equalText(v: any, innerText: string) {
return v.trim() === innerText
}
// TODO:
export function liveEditingSaveHander() {
export const liveEditingSaveHander: SaveHandler = {
condition: (prop) => {
const v = prop.getValue();
return prop.type === 'expression' || isI18nData(v);
},
onSaveContent: (content, prop) => {
const v = prop.getValue();
const locale = Env.getLocale();
let data = v;
if (isJSExpression(v)) {
data = v.mock;
}
if (isI18nData(data)) {
const i18n = data.key ? i18nUtil.getItem(data.key) : null;
if (i18n) {
i18n.setDoc(content, locale);
return;
}
data = {
...(data as any),
[locale]: content,
};
} else {
data = content;
}
if (isJSExpression(v)) {
prop.setValue({
type: 'JSExpression',
value: v.value,
mock: data,
});
} else {
prop.setValue(data);
}
}
}
// TODO:
// 非文本编辑
// 国际化数据,改变当前
// JSExpression, 改变 mock 或 弹出绑定变量

View File

@ -43,7 +43,7 @@ html {
}
html.engine-blur #engine {
-webkit-filter: blur(4px);
filter: blur(4px);
}
.engine-main {
@ -98,6 +98,11 @@ html.engine-blur #engine {
}
}
.vs-icon .vs-icon-del, .vs-icon .vs-icon-entry {
width: 16px!important;
height: 16px!important;
}
.lc-left-float-pane {
font-size: 14px;
}
@ -107,3 +112,13 @@ html.engine-preview-mode {
display: none !important;
}
}
.ve-popups .ve-message {
right: 290px;
.ve-message-content {
display: flex;
align-items: center;
line-height: 22px;
}
}

View File

@ -36,8 +36,8 @@ export default class LeftFloatPane extends Component<{ area: Area<any, Panel> }>
.contentWindow.document.documentElement.contains(target)) {
return false;
}
// 防止点击 popup / dialog 等触发失焦
if (!document.querySelector('.lc-workbench-center')?.contains(target)) {
// 点击非编辑区域的 popup / dialog 等,不触发失焦
if (!document.querySelector('.lc-workbench')?.contains(target)) {
return true;
}
const docks = area.current?.getAssocDocks();

View File

@ -294,7 +294,7 @@ export class Skeleton {
let { area } = parsedConfig;
if (!area) {
if (parsedConfig.type === 'Panel') {
area = 'leftFloatArea'
area = 'leftFloatArea';
} else if (parsedConfig.type === 'Widget') {
area = 'mainArea';
} else {

View File

@ -105,12 +105,13 @@ class Renderer extends Component<{ renderer: SimulatorRenderer }> {
customCreateElement={(Component: any, props: any, children: any) => {
const { __id, __desingMode, ...viewProps } = props;
viewProps.componentId = __id;
viewProps._leaf = host.document.getNode(__id);
const leaf = host.document.getNode(__id);
viewProps._leaf = leaf;
return createElement(
getDeviceView(Component, device, designMode),
viewProps,
children == null ? [] : Array.isArray(children) ? children : [children],
leaf?.isContainer() ? (children == null ? [] : Array.isArray(children) ? children : [children]) : null,
);
}}
onCompGetRef={(schema: any, ref: ReactInstance | null) => {

View File

@ -49,6 +49,11 @@ export interface FilterItem {
name: string;
filter: (target: SettingTarget, currentValue: any) => any;
}
export interface AutorunItem {
name: string;
autorun: (target: SettingTarget) => any;
}
export interface Experimental {
context?: { [contextInfoName: string]: any };
@ -57,8 +62,8 @@ export interface Experimental {
transducers?: any; // ? should support
initials?: InitialItem[];
filters?: FilterItem[];
autoruns?: AutorunItem[];
callbacks?: Callbacks;
// TODO: thinkof function
initialChildren?: NodeData[] | ((target: SettingTarget) => NodeData[]);
// 样式 及 位置handle上必须有明确的标识以便事件路由判断或者主动设置事件独占模式
@ -85,7 +90,7 @@ export interface Experimental {
liveTextEditing?: LiveTextEditingConfig[];
}
// thinkof Array
// thinkof Array
export interface LiveTextEditingConfig {
propTarget: string;
selector?: string;