mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-03-26 15:23:18 +00:00
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:
commit
76910386d9
@ -1,3 +1,4 @@
|
|||||||
export * from './host';
|
export * from './host';
|
||||||
export * from './host-view';
|
export * from './host-view';
|
||||||
export * from './renderer';
|
export * from './renderer';
|
||||||
|
export * from './live-editing/live-editing';
|
||||||
|
|||||||
@ -16,39 +16,59 @@ function defaultSaveContent(content: string, prop: Prop) {
|
|||||||
prop.setValue(content);
|
prop.setValue(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EditingTarget {
|
||||||
|
node: Node;
|
||||||
|
rootElement: HTMLElement;
|
||||||
|
event: MouseEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveHandlers: SaveHandler[] = [];
|
||||||
|
function addLiveEditingSaveHandler(handler: SaveHandler) {
|
||||||
|
saveHandlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
const specificRules: SpecificRule[] = [];
|
||||||
|
function addLiveEditingSpecificRule(rule: SpecificRule) {
|
||||||
|
specificRules.push(rule);
|
||||||
|
}
|
||||||
|
|
||||||
export class LiveEditing {
|
export class LiveEditing {
|
||||||
|
static addLiveEditingSpecificRule = addLiveEditingSpecificRule;
|
||||||
|
static addLiveEditingSaveHandler = addLiveEditingSaveHandler;
|
||||||
|
|
||||||
@obx.ref private _editing: Prop | null = null;
|
@obx.ref private _editing: Prop | null = null;
|
||||||
apply(target: { node: Node; rootElement: HTMLElement; event: MouseEvent }) {
|
apply(target: EditingTarget) {
|
||||||
const { node, event, rootElement } = target;
|
const { node, event, rootElement } = target;
|
||||||
const targetElement = event.target as HTMLElement;
|
const targetElement = event.target as HTMLElement;
|
||||||
const liveTextEditing = node.componentMeta.getMetadata().experimental?.liveTextEditing || [];
|
const liveTextEditing = node.componentMeta.liveTextEditing;
|
||||||
|
|
||||||
let setterPropElement = getSetterPropElement(targetElement, rootElement);
|
let setterPropElement = getSetterPropElement(targetElement, rootElement);
|
||||||
let propTarget = setterPropElement?.dataset.setterProp;
|
let propTarget = setterPropElement?.dataset.setterProp;
|
||||||
let matched: LiveTextEditingConfig | undefined;
|
let matched: (LiveTextEditingConfig & { propElement?: HTMLElement; }) | undefined | null;
|
||||||
if (propTarget) {
|
if (liveTextEditing) {
|
||||||
// 已埋点命中 data-setter-prop="proptarget", 从 liveTextEditing 读取配置(mode|onSaveContent)
|
if (propTarget) {
|
||||||
matched = liveTextEditing.find(config => config.propTarget == propTarget);
|
// 已埋点命中 data-setter-prop="proptarget", 从 liveTextEditing 读取配置(mode|onSaveContent)
|
||||||
} else {
|
matched = liveTextEditing.find(config => config.propTarget == propTarget);
|
||||||
// 执行 embedTextEditing selector 规则,获得第一个节点 是否 contains e.target,若匹配,读取配置
|
} else {
|
||||||
matched = liveTextEditing.find(config => {
|
// 执行 embedTextEditing selector 规则,获得第一个节点 是否 contains e.target,若匹配,读取配置
|
||||||
if (!config.selector) {
|
matched = liveTextEditing.find(config => {
|
||||||
return false;
|
if (!config.selector) {
|
||||||
}
|
|
||||||
setterPropElement = config.selector === ':root' ? rootElement : rootElement.querySelector(config.selector);
|
|
||||||
if (!setterPropElement) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!setterPropElement.contains(targetElement)) {
|
|
||||||
// try selectorAll
|
|
||||||
setterPropElement = Array.from(rootElement.querySelectorAll(config.selector)).find(item => item.contains(targetElement)) as HTMLElement;
|
|
||||||
if (!setterPropElement) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
setterPropElement = queryPropElement(rootElement, targetElement, config.selector);
|
||||||
return true;
|
return setterPropElement ? true : false;
|
||||||
|
});
|
||||||
|
propTarget = matched?.propTarget;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
specificRules.some((rule) => {
|
||||||
|
matched = rule(target);
|
||||||
|
return matched ? true : false;
|
||||||
});
|
});
|
||||||
propTarget = matched?.propTarget;
|
if (matched) {
|
||||||
|
propTarget = matched.propTarget;
|
||||||
|
setterPropElement = matched.propElement || queryPropElement(rootElement, targetElement, matched.selector);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!propTarget) {
|
if (!propTarget) {
|
||||||
@ -75,7 +95,7 @@ export class LiveEditing {
|
|||||||
// 4. 监听 blur 事件
|
// 4. 监听 blur 事件
|
||||||
// 5. 设置编辑锁定:disable hover | disable select | disable canvas drag
|
// 5. 设置编辑锁定:disable hover | disable select | disable canvas drag
|
||||||
|
|
||||||
const onSaveContent = matched?.onSaveContent || this.saveHandlers.find(item => item.condition(prop))?.onSaveContent || defaultSaveContent;
|
const onSaveContent = matched?.onSaveContent || saveHandlers.find(item => item.condition(prop))?.onSaveContent || defaultSaveContent;
|
||||||
|
|
||||||
setterPropElement.setAttribute('contenteditable', matched?.mode && matched.mode !== 'plaintext' ? 'true' : 'plaintext-only');
|
setterPropElement.setAttribute('contenteditable', matched?.mode && matched.mode !== 'plaintext' ? 'true' : 'plaintext-only');
|
||||||
setterPropElement.classList.add('engine-live-editing');
|
setterPropElement.classList.add('engine-live-editing');
|
||||||
@ -99,6 +119,8 @@ export class LiveEditing {
|
|||||||
this._editing = prop;
|
this._editing = prop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: process enter | esc events & joint the FocusTracker
|
||||||
|
|
||||||
// TODO: upward testing for b/i/a html elements
|
// TODO: upward testing for b/i/a html elements
|
||||||
|
|
||||||
// 非文本编辑
|
// 非文本编辑
|
||||||
@ -127,13 +149,12 @@ export class LiveEditing {
|
|||||||
}
|
}
|
||||||
this._editing = null;
|
this._editing = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveHandlers: SaveHandler[] = [];
|
|
||||||
setSaveHandler(handler: SaveHandler) {
|
|
||||||
this.saveHandlers.push(handler);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SpecificRule = (target: EditingTarget) => (LiveTextEditingConfig & {
|
||||||
|
propElement?: HTMLElement;
|
||||||
|
}) | null;
|
||||||
|
|
||||||
export interface SaveHandler {
|
export interface SaveHandler {
|
||||||
condition: (prop: Prop) => boolean;
|
condition: (prop: Prop) => boolean;
|
||||||
onSaveContent: (content: string, prop: Prop) => void;
|
onSaveContent: (content: string, prop: Prop) => void;
|
||||||
@ -155,3 +176,22 @@ function selectRange(doc: Document, range: Range) {
|
|||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function queryPropElement(rootElement: HTMLElement, targetElement: HTMLElement, selector?: string) {
|
||||||
|
if (!selector) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let propElement = selector === ':root' ? rootElement : rootElement.querySelector(selector);
|
||||||
|
if (!propElement) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!propElement.contains(targetElement)) {
|
||||||
|
// try selectorAll
|
||||||
|
propElement = Array.from(rootElement.querySelectorAll(selector)).find(item => item.contains(targetElement)) as HTMLElement;
|
||||||
|
if (!propElement) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return propElement as HTMLElement;
|
||||||
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import {
|
|||||||
NestingFilter,
|
NestingFilter,
|
||||||
isTitleConfig,
|
isTitleConfig,
|
||||||
I18nData,
|
I18nData,
|
||||||
|
LiveTextEditingConfig,
|
||||||
|
FieldConfig,
|
||||||
} from '@ali/lowcode-types';
|
} from '@ali/lowcode-types';
|
||||||
import { computed } from '@ali/lowcode-editor-core';
|
import { computed } from '@ali/lowcode-editor-core';
|
||||||
import { Node, ParentalNode } from './document';
|
import { Node, ParentalNode } from './document';
|
||||||
@ -91,6 +93,11 @@ export class ComponentMeta {
|
|||||||
return config?.combined || config?.props || [];
|
return config?.combined || config?.props || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _liveTextEditing?: LiveTextEditingConfig[];
|
||||||
|
get liveTextEditing() {
|
||||||
|
return this._liveTextEditing;
|
||||||
|
}
|
||||||
|
|
||||||
private parentWhitelist?: NestingFilter | null;
|
private parentWhitelist?: NestingFilter | null;
|
||||||
private childWhitelist?: NestingFilter | null;
|
private childWhitelist?: NestingFilter | null;
|
||||||
|
|
||||||
@ -150,6 +157,26 @@ export class ComponentMeta {
|
|||||||
: title;
|
: title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const liveTextEditing = this._transformedMetadata.experimental?.liveTextEditing || [];
|
||||||
|
|
||||||
|
function collectLiveTextEditing(items: FieldConfig[]) {
|
||||||
|
items.forEach(config => {
|
||||||
|
if (config.items) {
|
||||||
|
collectLiveTextEditing(config.items);
|
||||||
|
} else {
|
||||||
|
const liveConfig = config.liveTextEditing || config.extraProps?.liveTextEditing;
|
||||||
|
if (liveConfig) {
|
||||||
|
liveTextEditing.push({
|
||||||
|
propTarget: String(config.name),
|
||||||
|
...liveConfig,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
collectLiveTextEditing(this.configure);
|
||||||
|
this._liveTextEditing = liveTextEditing.length > 0 ? liveTextEditing : undefined;
|
||||||
|
|
||||||
const { configure = {} } = this._transformedMetadata;
|
const { configure = {} } = this._transformedMetadata;
|
||||||
this._acceptable = false;
|
this._acceptable = false;
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
|
import { isObject } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Icon } from '@alifd/next';
|
import { Icon } from '@alifd/next';
|
||||||
import { Title, Tip } from '@ali/lowcode-editor-core';
|
import { Title, Tip } from '@ali/lowcode-editor-core';
|
||||||
@ -7,6 +8,7 @@ import { PopupPipe, PopupContext } from '../popup';
|
|||||||
import { intl, intlNode } from '../../locale';
|
import { intl, intlNode } from '../../locale';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import { IconClear } from 'editor-skeleton/src/icons/clear';
|
import { IconClear } from 'editor-skeleton/src/icons/clear';
|
||||||
|
import InlineTip from './inlinetip';
|
||||||
|
|
||||||
export interface FieldProps {
|
export interface FieldProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -14,6 +16,8 @@ export interface FieldProps {
|
|||||||
defaultDisplay?: 'accordion' | 'inline' | 'block';
|
defaultDisplay?: 'accordion' | 'inline' | 'block';
|
||||||
collapsed?: boolean;
|
collapsed?: boolean;
|
||||||
valueState?: number;
|
valueState?: number;
|
||||||
|
name?: string;
|
||||||
|
tip?: any;
|
||||||
onExpandChange?: (expandState: boolean) => void;
|
onExpandChange?: (expandState: boolean) => void;
|
||||||
onClear?: () => void;
|
onClear?: () => void;
|
||||||
}
|
}
|
||||||
@ -76,11 +80,36 @@ export class Field extends Component<FieldProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTipContent(propName: string, tip?: any): any {
|
||||||
|
let tipContent = (
|
||||||
|
<div>
|
||||||
|
<div>属性:{propName}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isObject(tip)) {
|
||||||
|
tipContent = (
|
||||||
|
<div>
|
||||||
|
<div>属性:{propName}</div>
|
||||||
|
<div>说明:{tip.content}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (tip) {
|
||||||
|
tipContent = (
|
||||||
|
<div>
|
||||||
|
<div>属性:{propName}</div>
|
||||||
|
<div>说明:{tip}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return tipContent;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { className, children, title, valueState, onClear } = this.props;
|
const { className, children, title, valueState, onClear, name: propName, tip } = this.props;
|
||||||
const { display, collapsed } = this.state;
|
const { display, collapsed } = this.state;
|
||||||
const isAccordion = display === 'accordion';
|
const isAccordion = display === 'accordion';
|
||||||
|
const tipContent = this.getTipContent(propName, tip);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(`lc-field lc-${display}-field`, className, {
|
className={classNames(`lc-field lc-${display}-field`, className, {
|
||||||
@ -91,6 +120,7 @@ export class Field extends Component<FieldProps> {
|
|||||||
<div className="lc-field-title">
|
<div className="lc-field-title">
|
||||||
{createValueState(valueState, onClear)}
|
{createValueState(valueState, onClear)}
|
||||||
<Title title={title || ''} />
|
<Title title={title || ''} />
|
||||||
|
<InlineTip position="top">{tipContent}</InlineTip>
|
||||||
</div>
|
</div>
|
||||||
{isAccordion && <Icon className="lc-field-icon" type="arrow-up" size="xs" />}
|
{isAccordion && <Icon className="lc-field-icon" type="arrow-up" size="xs" />}
|
||||||
</div>
|
</div>
|
||||||
@ -115,7 +145,7 @@ export class Field extends Component<FieldProps> {
|
|||||||
*/
|
*/
|
||||||
function createValueState(valueState?: number, onClear?: () => void) {
|
function createValueState(valueState?: number, onClear?: () => void) {
|
||||||
let tip: any = null;
|
let tip: any = null;
|
||||||
let className: string = 'lc-valuestate';
|
let className = 'lc-valuestate';
|
||||||
let icon: any = null;
|
let icon: any = null;
|
||||||
if (valueState) {
|
if (valueState) {
|
||||||
if (valueState < 0) {
|
if (valueState < 0) {
|
||||||
@ -139,10 +169,12 @@ function createValueState(valueState?: number, onClear?: () => void) {
|
|||||||
// unset 占位空间
|
// unset 占位空间
|
||||||
}
|
}
|
||||||
|
|
||||||
return <i className={className} onClick={onClear}>
|
return (
|
||||||
{icon}
|
<i className={className} onClick={onClear}>
|
||||||
{tip && <Tip>{tip}</Tip>}
|
{icon}
|
||||||
</i>;
|
{tip && <Tip>{tip}</Tip>}
|
||||||
|
</i>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PopupFieldProps extends FieldProps {
|
export interface PopupFieldProps extends FieldProps {
|
||||||
|
|||||||
25
packages/editor-skeleton/src/components/field/inlinetip.tsx
Normal file
25
packages/editor-skeleton/src/components/field/inlinetip.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export interface InlineTipProps {
|
||||||
|
position: string;
|
||||||
|
theme?: 'green' | 'black';
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class InlineTip extends React.Component<InlineTipProps> {
|
||||||
|
static displayName = 'InlineTip';
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
position: 'auto',
|
||||||
|
theme: 'black',
|
||||||
|
};
|
||||||
|
|
||||||
|
render(): React.ReactNode {
|
||||||
|
const { position, theme, children } = this.props;
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'none' }} data-role="tip" data-position={position} data-theme={theme}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -66,6 +66,7 @@ class SettingFieldView extends Component<{ field: SettingField }> {
|
|||||||
valueState: field.isRequired ? 10 : field.valueState,
|
valueState: field.isRequired ? 10 : field.valueState,
|
||||||
onExpandChange: (expandState) => field.setExpanded(expandState),
|
onExpandChange: (expandState) => field.setExpanded(expandState),
|
||||||
onClear: () => field.clearValue(),
|
onClear: () => field.clearValue(),
|
||||||
|
...extraProps,
|
||||||
},
|
},
|
||||||
createSetterContent(setterType, {
|
createSetterContent(setterType, {
|
||||||
...shallowIntl(setterProps),
|
...shallowIntl(setterProps),
|
||||||
@ -91,7 +92,7 @@ class SettingFieldView extends Component<{ field: SettingField }> {
|
|||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
field.setValue(value);
|
field.setValue(value);
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
extraProps.forceInline ? 'plain' : extraProps.display,
|
extraProps.forceInline ? 'plain' : extraProps.display,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -88,7 +88,7 @@ body.engine-document {
|
|||||||
.engine-live-editing {
|
.engine-live-editing {
|
||||||
cursor: text;
|
cursor: text;
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 0 0px 4px rgba(23, 141, 247, 0.2);
|
box-shadow: 0 0 0px 2px rgb(102, 188, 92);
|
||||||
user-select: text;
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,13 @@ export interface FieldExtraProps {
|
|||||||
* compatiable vision display
|
* compatiable vision display
|
||||||
*/
|
*/
|
||||||
display?: 'accordion' | 'inline' | 'block' | 'plain' | 'popup' | 'entry';
|
display?: 'accordion' | 'inline' | 'block' | 'plain' | 'popup' | 'entry';
|
||||||
|
liveTextEditing?: {
|
||||||
|
selector: string;
|
||||||
|
// 编辑模式 纯文本|段落编辑|文章编辑(默认纯文本,无跟随工具条)
|
||||||
|
mode?: 'plaintext' | 'paragraph' | 'article';
|
||||||
|
// 从 contentEditable 获取内容并设置到属性
|
||||||
|
onSaveContent?: (content: string, prop: any) => any;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FieldConfig extends FieldExtraProps {
|
export interface FieldConfig extends FieldExtraProps {
|
||||||
|
|||||||
@ -22,7 +22,7 @@ const GlobalPropsConfigure: Array<{ position: string; initials?: InitialItem[];
|
|||||||
const Overrides: {
|
const Overrides: {
|
||||||
[componentName: string]: {
|
[componentName: string]: {
|
||||||
initials?: InitialItem[];
|
initials?: InitialItem[];
|
||||||
config: any;
|
override: any;
|
||||||
};
|
};
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
@ -44,14 +44,23 @@ function removeGlobalPropsConfigure(name: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function overridePropsConfigure(componentName: string, config: OldPropConfig | OldPropConfig[]) {
|
function overridePropsConfigure(componentName: string, config: { [name: string]: OldPropConfig } | OldPropConfig[]) {
|
||||||
const initials: InitialItem[] = [];
|
const initials: InitialItem[] = [];
|
||||||
const addInitial = (item: InitialItem) => {
|
const addInitial = (item: InitialItem) => {
|
||||||
initials.push(item);
|
initials.push(item);
|
||||||
};
|
};
|
||||||
|
let override: any;
|
||||||
|
if (Array.isArray(config)) {
|
||||||
|
override = upgradeConfigure(config, addInitial);
|
||||||
|
} else {
|
||||||
|
override = {};
|
||||||
|
Object.keys(config).forEach(key => {
|
||||||
|
override[key] = upgradePropConfig(config[key], addInitial);
|
||||||
|
});
|
||||||
|
}
|
||||||
Overrides[componentName] = {
|
Overrides[componentName] = {
|
||||||
initials,
|
initials,
|
||||||
config: Array.isArray(config) ? upgradeConfigure(config, addInitial) : upgradePropConfig(config, addInitial),
|
override,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
registerMetadataTransducer(
|
registerMetadataTransducer(
|
||||||
@ -82,18 +91,18 @@ registerMetadataTransducer(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const override = Overrides[componentName];
|
const override = Overrides[componentName]?.override;
|
||||||
if (override) {
|
if (override) {
|
||||||
if (Array.isArray(override.config)) {
|
if (Array.isArray(override)) {
|
||||||
metadata.configure.combined = override.config;
|
metadata.configure.combined = override;
|
||||||
} else {
|
} else {
|
||||||
let l = top.length;
|
let l = top.length;
|
||||||
let item;
|
let item;
|
||||||
while (l-- > 0) {
|
while (l-- > 0) {
|
||||||
item = top[l];
|
item = top[l];
|
||||||
if (item.name in override) {
|
if (item.name in override) {
|
||||||
if (override.config[item.name]) {
|
if (override[item.name]) {
|
||||||
top.splice(l, 1, override.config[item.name]);
|
top.splice(l, 1, override[item.name]);
|
||||||
} else {
|
} else {
|
||||||
top.splice(l, 1);
|
top.splice(l, 1);
|
||||||
}
|
}
|
||||||
@ -102,7 +111,6 @@ registerMetadataTransducer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO FIXME! append override & globalConfigure initials and then unique
|
|
||||||
return metadata;
|
return metadata;
|
||||||
},
|
},
|
||||||
100,
|
100,
|
||||||
@ -249,7 +257,7 @@ class Prototype {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getRectSelector() {
|
getRectSelector() {
|
||||||
return this.meta.rectSelector;
|
return this.meta.rootSelector;
|
||||||
}
|
}
|
||||||
|
|
||||||
isContainer() {
|
isContainer() {
|
||||||
|
|||||||
@ -92,6 +92,7 @@ export interface OldPropConfig {
|
|||||||
slotTitle?: string;
|
slotTitle?: string;
|
||||||
initialChildren?: any; // schema
|
initialChildren?: any; // schema
|
||||||
allowTextInput: boolean;
|
allowTextInput: boolean;
|
||||||
|
liveTextEditing?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
// from vision 5.4
|
// from vision 5.4
|
||||||
@ -205,6 +206,7 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
|
|||||||
setter,
|
setter,
|
||||||
useVariableChange,
|
useVariableChange,
|
||||||
supportVariable,
|
supportVariable,
|
||||||
|
liveTextEditing,
|
||||||
} = config;
|
} = config;
|
||||||
|
|
||||||
const extraProps: any = {};
|
const extraProps: any = {};
|
||||||
@ -451,6 +453,10 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
|
|||||||
}
|
}
|
||||||
newConfig.setter = primarySetter;
|
newConfig.setter = primarySetter;
|
||||||
|
|
||||||
|
if (liveTextEditing) {
|
||||||
|
extraProps.liveTextEditing = liveTextEditing;
|
||||||
|
}
|
||||||
|
|
||||||
return newConfig;
|
return newConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { isJSBlock, isJSExpression, isJSSlot } from '@ali/lowcode-types';
|
import { isJSBlock, isJSExpression, isJSSlot } from '@ali/lowcode-types';
|
||||||
import { isPlainObject } from '@ali/lowcode-utils';
|
import { isPlainObject } from '@ali/lowcode-utils';
|
||||||
import { globalContext, Editor } from '@ali/lowcode-editor-core';
|
import { globalContext, Editor } from '@ali/lowcode-editor-core';
|
||||||
import { Designer, TransformStage, addBuiltinComponentAction } from '@ali/lowcode-designer';
|
import { Designer, LiveEditing, TransformStage, addBuiltinComponentAction } from '@ali/lowcode-designer';
|
||||||
import Outline, { OutlineBackupPane, getTreeMaster } from '@ali/lowcode-plugin-outline-pane';
|
import Outline, { OutlineBackupPane, getTreeMaster } from '@ali/lowcode-plugin-outline-pane';
|
||||||
import { toCss } from '@ali/vu-css-style';
|
import { toCss } from '@ali/vu-css-style';
|
||||||
|
|
||||||
@ -10,6 +10,7 @@ import { Skeleton, SettingsPrimaryPane } from '@ali/lowcode-editor-skeleton';
|
|||||||
|
|
||||||
import { i18nReducer } from './i18n-reducer';
|
import { i18nReducer } from './i18n-reducer';
|
||||||
import { InstanceNodeSelector } from './components';
|
import { InstanceNodeSelector } from './components';
|
||||||
|
import { liveEditingRule } from './vc-live-editing';
|
||||||
|
|
||||||
export const editor = new Editor();
|
export const editor = new Editor();
|
||||||
globalContext.register(editor, Editor);
|
globalContext.register(editor, Editor);
|
||||||
@ -176,20 +177,7 @@ skeleton.add({
|
|||||||
content: OutlineBackupPane,
|
content: OutlineBackupPane,
|
||||||
});
|
});
|
||||||
|
|
||||||
// skeleton.add({
|
LiveEditing.addLiveEditingSpecificRule(liveEditingRule);
|
||||||
// name: 'sourceEditor',
|
|
||||||
// type: 'PanelDock',
|
|
||||||
// props: {
|
|
||||||
// align: 'top',
|
|
||||||
// icon: 'code',
|
|
||||||
// description: '组件库',
|
|
||||||
// },
|
|
||||||
// panelProps: {
|
|
||||||
// width: 500
|
|
||||||
// // area: 'leftFixedArea'
|
|
||||||
// },
|
|
||||||
// content: SourceEditor,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// 实例节点选择器,线框高亮
|
// 实例节点选择器,线框高亮
|
||||||
addBuiltinComponentAction({
|
addBuiltinComponentAction({
|
||||||
|
|||||||
72
packages/vision-preset/src/vc-live-editing.ts
Normal file
72
packages/vision-preset/src/vc-live-editing.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { EditingTarget, Node as DocNode } from '@ali/lowcode-designer';
|
||||||
|
import Env from './env';
|
||||||
|
import { isJSExpression } from '@ali/lowcode-types';
|
||||||
|
const I18nUtil = require('@ali/ve-i18n-util');
|
||||||
|
|
||||||
|
interface I18nObject {
|
||||||
|
type?: string;
|
||||||
|
use?: string;
|
||||||
|
key?: string;
|
||||||
|
[lang: string]: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getI18nText(obj: I18nObject) {
|
||||||
|
let locale = Env.getLocale();
|
||||||
|
if (obj.key) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getText(node: DocNode, prop: string) {
|
||||||
|
const p = node.getProp(prop, false);
|
||||||
|
if (!p || p.isUnset()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const v = p.getValue();
|
||||||
|
if (v == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (p.type === 'literal') {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
if ((v as any).type === 'i18n') {
|
||||||
|
return getI18nText(v as any);
|
||||||
|
}
|
||||||
|
if (isJSExpression(v)) {
|
||||||
|
return v.mock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function liveEditingRule(target: EditingTarget) {
|
||||||
|
// for vision components specific
|
||||||
|
const { node, rootElement, event } = target;
|
||||||
|
|
||||||
|
const targetElement = event.target as HTMLElement;
|
||||||
|
|
||||||
|
if (!Array.from(targetElement.childNodes).every(item => item.nodeType === Node.TEXT_NODE)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const innerText = targetElement.innerText;
|
||||||
|
const propTarget = ['title', 'label', 'text', 'content'].find(prop => {
|
||||||
|
// TODO: enhance compare text logic
|
||||||
|
return getText(node, prop) === innerText;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (propTarget) {
|
||||||
|
return {
|
||||||
|
propElement: targetElement,
|
||||||
|
propTarget,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
export function liveEditingSaveHander() {
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user