Merge branches 'polyfill/vision', 'polyfill/vision' and 'polyfill/vision' of gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine into polyfill/vision

This commit is contained in:
mario.gk 2020-05-04 16:48:22 +08:00
commit 550fdde7f7
16 changed files with 1342 additions and 166 deletions

View File

@ -33,6 +33,9 @@
"@ali/ve-trunk-pane": "^5.1.0-beta.14",
"@ali/vs-variable-setter": "^3.1.0",
"@ali/vu-legao-design-fetch-context": "^1.0.3",
"@ali/ve-page-history": "1.2.0",
"@ali/ve-history-pane": "4.0.0",
"@ali/ve-page-history-pane": "^5.0.0-beta.0",
"@alifd/next": "^1.19.12",
"@alife/theme-lowcode-dark": "^0.1.0",
"@alife/theme-lowcode-light": "^0.1.0",

View File

@ -5,6 +5,9 @@ import Engine, { Panes } from '@ali/visualengine';
import { ActionUtil as actionUtil } from '@ali/visualengine-utils';
import getTrunkPane from '@ali/ve-trunk-pane';
import DatapoolPane from '@ali/ve-datapool-pane';
import PageHistoryManager from '@ali/ve-page-history';
import HistoryPane from '@ali/ve-history-pane';
import PageHistoryPane from '@ali/ve-page-history-pane';
// import I18nPane from '@ali/ve-i18n-pane';
import I18nManagePane from '@ali/ve-i18n-manage-pane';
import ActionPane from '@ali/ve-action-pane';
@ -322,6 +325,98 @@ function initActionPane() {
});
}
// 操作历史与页面历史面板
function initHistoryPane() {
// let historyConfigs = {getDesignerModuleConfigs(
// this.designerConfigs,
// 'history',
// )};
let historyConfigs = {
enableRedoAndUndo: true,
enablePageHistory: true,
};;
// if (!historyConfigs) {
// return;
// }
// if (historyConfigs === true) {
// historyConfigs = {
// enableRedoAndUndo: true,
// enablePageHistory: true,
// };
// }
// if (historyConfigs.enableRedoAndUndo === undefined) {
// historyConfigs.enableRedoAndUndo = true;
// }
// if (historyConfigs.enablePageHistory === undefined) {
// historyConfigs.enablePageHistory = true;
// }
const isDemoMode = false;
const isEnvSupportsHistoryPane = true;
const historyManager = PageHistoryManager.getManager();
console.log('PageHistoryManager', historyManager);
console.log('PageHistoryManager.onOpenPane', historyManager.onOpenPane);
// 历史撤销、重做以及唤起页面历史按钮
if (typeof HistoryPane === 'function') {
// const historyPane = {
// ...HistoryPane({
// showPageHistory:
// isEnvSupportsHistoryPane
// // && this.app.isForm()
// && !isDemoMode,
// historyManager,
// historyConfigs,
// }),
// index: -940,
// };
// console.log('aaaaaa', historyPane);
Panes.add(HistoryPane, {
props : {
showPageHistory:
isEnvSupportsHistoryPane
// && this.app.isForm()
&& !isDemoMode,
historyManager,
historyConfigs,
index: -940,
}
});
} else {
Panes.add(HistoryPane, {
index: -940,
});
}
// 页面历史 UI 面板
if (
PageHistoryPane
&& !isDemoMode
&& isEnvSupportsHistoryPane
) {
console.log(1111, PageHistoryPane({
historyManager: PageHistoryManager.getManager(),
app: {},
}))
Panes.add(PageHistoryPane, {
props : {
historyManager: {
historyManager,
app: {
}
},
index: -940,
},
});
}
}
async function init() {
Engine.Env.setEnv('RE_VERSION', '7.2.0');
Engine.Env.setSupportFeatures({
@ -335,6 +430,7 @@ async function init() {
initI18nPane();
initActionPane();
initDemoPanes();
initHistoryPane();
Engine.init();
}

View File

@ -1,8 +1,11 @@
declare module "@ali/visualengine";
declare module "@ali/visualengine-utils";
declare module "@ali/ve-trunk-pane";
declare module "@ali/vs-variable-setter";
declare module "@ali/ve-datapool-pane";
declare module "@ali/ve-i18n-manage-pane";
declare module "@ali/ve-action-pane";
declare module "@ali/vu-legao-design-fetch-context";
declare module '@ali/visualengine';
declare module '@ali/visualengine-utils';
declare module '@ali/ve-trunk-pane';
declare module '@ali/vs-variable-setter';
declare module '@ali/ve-datapool-pane';
declare module '@ali/ve-history-pane';
declare module '@ali/ve-page-history-pane';
declare module '@ali/ve-page-history';
declare module '@ali/ve-i18n-manage-pane';
declare module '@ali/ve-action-pane';
declare module '@ali/vu-legao-design-fetch-context';

View File

@ -465,6 +465,14 @@ export class DocumentModel {
getRoot() {
return this.rootNode;
}
/**
* vision
*/
getHistory(): History {
return this.history;
}
get root() {
return this.rootNode;
}

View File

@ -1,155 +0,0 @@
import { Component, ReactNode } from 'react';
import {
PopupField,
Field as NormalField,
EntryField,
PlainField,
createSettingFieldView,
SettingsPane,
createField,
} from '@ali/lowcode-editor-skeleton';
import { createSetterContent } from '@ali/lowcode-editor-core';
import { isPlainObject } from '@ali/lowcode-utils';
import { isSetterConfig } from '@ali/lowcode-types';
import context from './context';
import { VE_HOOKS } from './base/const';
export class Placeholder extends Component {
render() {
console.info(this.props);
return 'rending placeholder here';
}
}
export class SettingField extends Component<{
prop: any;
selected?: boolean;
forceDisplay?: string;
className?: string;
children?: ReactNode;
compact?: boolean;
key?: string;
addonProps?: object;
}> {
constructor(props: any) {
super(props);
console.info(props);
}
render() {
const { prop, selected, addonProps } = this.props;
const display = this.props.forceDisplay || prop.getDisplay();
if (display === 'none') {
return null;
}
// 标准的属性,即每一个 Field 在 VE 下都拥有的属性
const standardProps = {
className: this.props.className,
compact: this.props.compact,
isSupportMultiSetter: this.supportMultiSetter(),
isSupportVariable: prop.isSupportVariable(),
isUseVariable: prop.isUseVariable(),
prop,
setUseVariable: () => prop.setUseVariable(!prop.isUseVariable()),
tip: prop.getTip(),
title: prop.getTitle(),
};
// 部分 Field 所需要的额外 fieldProps
const extraProps = {};
const ctx = context;
const plugin = ctx.getPlugin(VE_HOOKS.VE_SETTING_FIELD_PROVIDER);
let Field;
if (typeof plugin === 'function') {
Field = plugin(display, FIELD_TYPE_MAP, prop);
}
if (!Field) {
Field = FIELD_TYPE_MAP[display] || PlainField;
}
createField()
this._prepareProps(display, extraProps);
if (display === 'entry') {
return <Field {...{ ...standardProps, ...extraProps }} />;
}
let setter;
const props: any = {
prop,
selected,
};
const fieldProps = { ...standardProps, ...extraProps };
if (prop.isUseVariable() && !this.variableSetter.isPopup) {
props.placeholder = '请输入表达式: ${var}';
props.key = `${prop.getId()}-variable`;
setter = React.createElement(this.variableSetter, props);
return <Field {...fieldProps}>{setter}</Field>;
}
// for composited prop
if (prop.getVisibleItems) {
setter = prop
.getVisibleItems()
.map((item: any) => <SettingField {...{ key: item.getId(), prop: item, selected }} />);
return <Field {...fieldProps}>{setter}</Field>;
}
setter = createSetterContent(prop.getSetter(), {
...addonProps,
...props,
});
return <Field {...fieldProps}>{setter}</Field>;
}
private supportMultiSetter() {
const { prop } = this.props;
const setter = prop && prop.getConfig && prop.getConfig('setter');
return prop.isSupportVariable() || Array.isArray(setter);
}
private _prepareProps(displayType: string, extraProps: IExtraProps): void {
const { prop } = this.props;
extraProps.propName = prop.isGroup() ? '组合属性,无属性名称' : prop.getName();
switch (displayType) {
case 'title':
break;
case 'block':
assign(extraProps, { isGroup: prop.isGroup() });
break;
case 'accordion':
assign(extraProps, {
headDIY: true,
isExpand: prop.isExpand(),
isGroup: prop.isGroup(),
onExpandChange: () => prop.onExpandChange(() => this.forceUpdate()),
toggleExpand: () => {
prop.toggleExpand();
},
});
break;
case 'entry':
assign(extraProps, { stageName: prop.getName() });
break;
default:
break;
}
}
}
const Field = {
SettingField: Placeholder,
Stage: Placeholder,
PopupField: Placeholder,
EntryField: Placeholder,
AccordionField: Placeholder,
BlockField: Placeholder,
InlineField: Placeholder,
};
export default Field;

View File

@ -0,0 +1,150 @@
import classnames from 'classnames';
import * as React from 'react';
import { Component } from 'react';
import InlineTip from './inlinetip';
import { isPlainObject } from '@ali/lowcode-utils';
interface IHelpTip {
url?: string;
content?: string;
}
function splitWord(title: string): JSX.Element[] {
return (title || '').split('').map((w, i) => <b key={`word${i}`} className='engine-word'>{w}</b>);
}
function getFieldTitle(title: string, tip: IHelpTip, compact?: boolean, propName?: string): JSX.Element {
const className = classnames('engine-field-title', { 've-compact': compact });
let titleContent = null;
if (!compact && typeof title === 'string') {
titleContent = splitWord(title);
}
let tipUrl = null;
let tipContent = null;
tipContent = (
<div>
<div>{propName}</div>
</div>
);
if (isPlainObject(tip)) {
tipUrl = tip.url;
tipContent = (
<div>
<div>{propName}</div>
<div>{tip.content}</div>
</div>
);
} else if (tip) {
tipContent = (
<div>
<div>{propName}</div>
<div>{tip}</div>
</div>
);
}
return (
<a
className={className}
target='_blank'
rel='noopener noreferrer'
href={tipUrl!}
>
{titleContent || (typeof title === 'object' ? '' : title)}
<InlineTip position='top'>{tipContent}</InlineTip>
</a>
);
}
export interface IVEFieldProps {
prop: any;
children: JSX.Element | string;
title?: string;
tip?: any;
propName?: string;
className?: string;
compact?: boolean;
stageName?: string;
/**
* render the top-header by jsx
*/
headDIY?: boolean;
isSupportVariable?: boolean;
isSupportMultiSetter?: boolean;
isUseVariable?: boolean;
isGroup?: boolean;
isExpand?: boolean;
toggleExpand?: () => any;
onExpandChange?: (fn: () => any) => any;
}
interface IVEFieldState {
hasError?: boolean;
}
export default class VEField extends Component<IVEFieldProps, IVEFieldState> {
public static displayName = 'VEField';
public readonly props: IVEFieldProps;
public classNames: string[] = [];
public state: IVEFieldState = {
hasError: false,
};
public componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error(error);
console.warn(info.componentStack);
}
public renderHead(): JSX.Element | JSX.Element[] | null {
const { title, tip, compact, propName } = this.props;
return getFieldTitle(title!, tip, compact, propName);
}
public renderBody(): JSX.Element | string {
return this.props.children;
}
public renderFoot(): any {
return null;
}
public render(): JSX.Element {
const { stageName, headDIY } = this.props;
const classNameList = classnames(...this.classNames, this.props.className);
const fieldProps: any = {};
if (stageName) {
// 为 stage 切换奠定基础
fieldProps['data-stage-target'] = this.props.stageName;
}
if (this.state.hasError) {
return (
<div>Field render error, please open console to find out.</div>
);
}
const headContent = headDIY ? this.renderHead()
: <div className='engine-field-head'>{this.renderHead()}</div>;
return (
<div className={classNameList} { ...fieldProps }>
{headContent}
<div className='engine-field-body'>
{this.renderBody()}
</div>
<div className='engine-field-foot'>
{this.renderFoot()}
</div>
</div>
);
}
}

View File

@ -0,0 +1,272 @@
@import '~@ali/ve-less-variables/index.less';
.engine-setting-field {
white-space: nowrap;
position: relative;
&:after, &:before {
content: " ";
display: table;
}
&:after {
clear: both;
}
.engine-field-title {
font-size: 12px;
font-family: @font-family;
line-height: 1em;
user-select: none;
color: var(--color-text, @dark-alpha-3);
width: fit-content;
white-space: initial;
word-break: break-word;
&::first-letter {
text-transform: capitalize;
}
.engine-word {
flex: 1;
text-align: center;
font-weight: normal;
&:first-child {
text-align: left;
}
&:last-of-type {
text-align: right;
}
&:only-of-type {
text-align: center;
}
overflow: hidden;
}
}
a.engine-field-title {
border-bottom: 1px dashed var(--color-line-normal, @normal-alpha-7);
text-decoration: none;
padding-bottom: 2px;
&:hover {
cursor: help;
}
}
.engine-field-variable-wrapper {
margin-left: 5px;
}
.engine-field-variable {
cursor: pointer;
opacity: 0.6;
&.engine-active {
opacity: 1;
color: var(--color-brand, @brand-color-1);
}
}
.engine-field-head {
padding-left: 10px;
height: 32px;
background: var(--color-block-background-shallow, @normal-alpha-8);
display: flex;
align-items: center;
font-weight: 500;
border-top: 1px solid var(--color-line-normal, @normal-alpha-7);
border-bottom: 1px solid var(--color-line-normal, @normal-alpha-7);
color: var(--color-title, @dark-alpha-2);
>.engine-icontip {
margin-left: 2px;
}
}
.engine-field-body {
min-height: 20px;
margin: 6px 0;
&:after, &:before {
content: " ";
display: table;
}
&:after {
clear: both;
}
.engine-field-head {
height: 28px;
border: none;
font-weight: 400;
}
}
&.engine-plain-field {
>.engine-field-variable {
position: absolute;
right: 5px;
top: 8px;
}
&:hover {
>.engine-field-variable {
opacity: 1;
}
}
}
&.engine-entry-field {
cursor: pointer;
display: flex;
align-items: center;
height: 32px;
padding-left: 10px;
font-weight: 500;
border-top: 1px solid var(--color-line-normal, @normal-alpha-7);
border-bottom: 1px solid var(--color-line-normal, @normal-alpha-7);
background: var(--color-block-background-shallow, @normal-alpha-8);
margin-bottom: 6px;
>.engine-field-title {
letter-spacing: 1px;
}
>.engine-icontip {
margin-left: 2px;
}
>.engine-field-arrow {
position: absolute;
right: 5px;
top: 50%;
transform: translateY(-50%) rotate(-90deg);
opacity: 0.4;
}
&:hover {
>.engine-field-arrow {
opacity: 1;
}
}
}
&.engine-popup-field {
cursor: pointer;
display: flex;
align-items: center;
height: 32px;
padding-left: 10px;
background: var(--color-block-background-shallow, @normal-alpha-8);
margin-bottom: 1px;
>.engine-field-title {
letter-spacing: 1px;
}
>.engine-icontip {
margin-left: 2px;
}
>.engine-field-icon {
position: absolute;
right: 5px;
top: 50%;
transform: translateY(-50%);
opacity: 0.6;
}
&:hover {
>.engine-field-icon {
opacity: 1;
}
}
}
&.engine-block-field {
>.engine-field-head{
> .engine-field-title {
letter-spacing: 1px;
}
>.engine-field-variable {
margin-left: 2px;
}
}
>.engine-field-body {
margin: 6px;
}
}
&.engine-inline-field {
display: flex;
align-items: center;
margin: 10px;
>.engine-field-head {
display: inline-flex;
background: none;
padding: 0;
border: none;
>.engine-field-title {
display: inline-flex;
width: 50px;
margin-right: 5px;
}
}
>.engine-field-body {
width: 100%;
display: inline-flex;
align-items: flex-start;
padding: 0;
margin: 0;
flex: 1;
position: relative;
}
>.engine-field-variable {
margin-left: 2px;
}
&:hover {
>.engine-field-variable {
opacity: 1;
}
}
}
&.engine-accordion-field {
>.engine-field-head {
position: relative;
cursor: pointer;
>.engine-field-title {
letter-spacing: 1px;
}
>.engine-field-arrow {
transform: rotate(180deg);
position: absolute;
right: 7px;
top: 7px;
transition: transform 0.1s ease;
opacity: 0.6;
}
>.engine-field-variable {
margin-left: 2px;
}
}
&.engine-collapsed {
>.engine-field-head {
margin-bottom: 6px;
}
>.engine-field-head > .engine-field-arrow {
transform: rotate(0);
}
>.engine-field-body {
display: none;
}
}
>.engine-field-body {
margin: 6px;
}
}
}
.engine-block-field,.engine-accordion-field,.engine-entry-field {
.engine-input-control {
margin: 10px;
}
}
.engine-field-tip-icon {
margin-left: 2px;
}

View File

@ -0,0 +1,376 @@
import Icons from '@ali/ve-icons';
import classNames from 'classnames';
import { Component } from 'react';
import { testType } from '@ali/ve-utils';
import VEField, { IVEFieldProps } from './field';
import { SettingField } from './settingField';
import VariableSwitcher from './variableSwitcher';
import popups from '@ali/ve-popups';
import './fields.less';
interface IHelpTip {
url?: string;
content?: string | JSX.Element;
}
function renderTip(tip: IHelpTip, prop?: { propName?: string }) {
const propName = prop && prop.propName;
if (!tip) {
return (
<Icons.Tip position="top" key="icon" className="engine-field-tip-icon">
<div>
<div>{propName}</div>
</div>
</Icons.Tip>
);
}
if (testType(tip) === 'object') {
return (
<Icons.Tip position="top" url={tip.url} key="icon-tip" className="engine-field-tip-icon">
<div>
<div>{propName}</div>
<div>{tip.content}</div>
</div>
</Icons.Tip>
);
}
return (
<Icons.Tip position="top" key="icon" className="engine-field-tip-icon">
<div>
<div>{propName}</div>
<div>{tip}</div>
</div>
</Icons.Tip>
);
}
export class PlainField extends VEField {
public static defaultProps = {
headDIY: true,
};
public static displayName: string = 'PlainField';
public renderHead(): null {
return null;
}
}
export class InlineField extends VEField {
public static displayName = 'InlineField';
constructor(props: any) {
super(props);
this.classNames = ['engine-setting-field', 'engine-inline-field'];
}
public renderFoot() {
return (
<div className="engine-field-variable-wrapper">
<VariableSwitcher {...this.props} />
</div>
);
}
}
export class BlockField extends VEField {
public static displayName = 'BlockField';
constructor(props: IVEFieldProps) {
super(props);
this.classNames = ['engine-setting-field', 'engine-block-field', props.isGroup ? 'engine-group-field' : ''];
}
public renderHead() {
const { title, tip, propName } = this.props;
return [
<span className="engine-field-title" key={title}>
{title}
</span>,
renderTip(tip, { propName }),
<VariableSwitcher {...this.props} />,
];
}
}
export class AccordionField extends VEField {
public readonly props: IVEFieldProps;
private willDetach?: () => any;
constructor(props: IVEFieldProps) {
super(props);
this._generateClassNames(props);
if (this.props.onExpandChange) {
this.willDetach = this.props.onExpandChange(() => this.forceUpdate());
}
}
public componentWillReceiveProps(nextProps: IVEFieldProps) {
this.classNames = this._generateClassNames(nextProps);
}
public componentWillUnmount() {
if (this.willDetach) {
this.willDetach();
}
}
public renderHead() {
const { title, tip, toggleExpand, propName } = this.props;
return (
<div className="engine-field-head" onClick={() => toggleExpand && toggleExpand()}>
<Icons name="arrow" className="engine-field-arrow" size="12px" />
<span className="engine-field-title">{title}</span>
{renderTip(tip, { propName })}
{<VariableSwitcher {...this.props} />}
</div>
);
}
private _generateClassNames(props: IVEFieldProps) {
this.classNames = [
'engine-setting-field',
'engine-accordion-field',
props.isGroup ? 'engine-group-field' : '',
!props.isExpand ? 'engine-collapsed' : '',
];
return this.classNames;
}
}
export class EntryField extends VEField {
constructor(props: any) {
super(props);
this.classNames = ['engine-setting-field', 'engine-entry-field'];
}
public render() {
const { propName, stageName, tip, title } = this.props;
const classNameList = classNames(...this.classNames, this.props.className);
const fieldProps: any = {};
if (stageName) {
// 为 stage 切换奠定基础
fieldProps['data-stage-target'] = this.props.stageName;
}
const innerElements = [
<span className="engine-field-title" key="field-title">
{title}
</span>,
renderTip(tip, { propName }),
<Icons name="arrow" className="engine-field-arrow" size="12px" key="engine-field-arrow-icon" />,
];
return (
<div className={classNameList} {...fieldProps}>
{innerElements}
</div>
);
}
}
export class PopupField extends VEField {
constructor(props: any) {
super(props);
this.classNames = ['engine-setting-field', 'engine-popup-field'];
}
public renderBody() {
return '';
}
public render() {
const { propName, stageName, tip, title } = this.props;
const classNameList = classNames(...this.classNames, this.props.className);
const fieldProps: any = {};
if (stageName) {
// 为 stage 切换奠定基础
fieldProps['data-stage-target'] = this.props.stageName;
}
return (
<div
className={classNameList}
onClick={(e) =>
popups.popup({
cancelOnBlur: true,
content: this.props.children,
position: 'left bottom',
showClose: true,
sizeFixed: true,
target: e.currentTarget,
})
}
>
<span className="engine-field-title">{title}</span>
{renderTip(tip, { propName })}
<VariableSwitcher {...this.props} />
<Icons name="popup" className="engine-field-icon" size="medium" />
</div>
);
}
}
export class CaptionField extends VEField {
constructor(props: IVEFieldProps) {
super(props);
this.classNames = ['engine-setting-field', 'engine-caption-field'];
}
public renderHead() {
const { title, tip, propName } = this.props;
return (
<div>
<span className="engine-field-title">{title}</span>
{renderTip(tip, { propName })}
</div>
);
}
}
export class Stage extends Component {
public readonly props: {
key: any;
stage: any;
current?: boolean;
direction?: any;
};
public stage: any;
public additionClassName: string;
public shell: Element | null = null;
private willDetach: () => any;
public componentWillMount() {
this.stage = this.props.stage;
if (this.stage.onCurrentTabChange) {
this.willDetach = this.stage.onCurrentTabChange(() => this.forceUpdate());
}
}
public componentDidMount() {
this.doSkate();
}
public componentWillReceiveProps(props: any) {
if (props.stage !== this.stage) {
this.stage = props.stage;
if (this.willDetach) {
this.willDetach();
}
if (this.stage.onCurrentTabChange) {
this.willDetach = this.stage.onCurrentTabChange(() => this.forceUpdate());
}
}
}
public componentDidUpdate() {
this.doSkate();
}
public componentWillUnmount() {
if (this.willDetach) {
this.willDetach();
}
}
public doSkate() {
if (this.additionClassName) {
setTimeout(() => {
const elem = this.shell;
if (elem && elem.classList) {
if (this.props.current) {
elem.classList.remove(this.additionClassName);
} else {
elem.classList.add(this.additionClassName);
}
this.additionClassName = '';
}
}, 10);
}
}
public render() {
const stage = this.stage;
let content = null;
let tabs = null;
let className = 'engine-settings-stage';
if (stage.getTabs) {
const selected = stage.getNode();
// stat for cache
stage.stat();
const currentTab = stage.getCurrentTab();
if (stage.hasTabs()) {
className += ' engine-has-tabs';
tabs = (
<div className="engine-settings-tabs">
{stage.getTabs().map((tab: any) => (
<div
key={tab.getId()}
className={`engine-settings-tab${tab === currentTab ? ' engine-active' : ''}`}
onClick={() => stage.setCurrentTab(tab)}
>
{tab.getTitle()}
{renderTip(tab.getTip())}
</div>
))}
</div>
);
}
if (currentTab) {
if (currentTab.getVisibleItems) {
content = currentTab
.getVisibleItems()
.map((item: any) => <SettingField key={item.getId()} selected={selected} prop={item} />);
} else if (currentTab.getSetter) {
content = (
<SettingField key={currentTab.getId()} selected={selected} prop={currentTab} forceDisplay="plain" />
);
}
}
} else {
content = stage.getContent();
}
if (this.props.current) {
if (this.props.direction) {
this.additionClassName = `engine-stagein-${this.props.direction}`;
className += ` ${this.additionClassName}`;
}
} else if (this.props.direction) {
this.additionClassName = `engine-stageout-${this.props.direction}`;
}
let stageBacker = null;
if (stage.hasBack()) {
className += ' engine-has-backer';
stageBacker = (
<div className="engine-settings-stagebacker" data-stage-target="stageback">
<Icons name="arrow" className="engine-field-arrow" size="12px" />
<span className="engine-field-title">{stage.getTitle()}</span>
{renderTip(stage.getTip())}
</div>
);
}
return (
<div
ref={(ref) => {
this.shell = ref;
}}
className={className}
>
{stageBacker}
{tabs}
<div className="engine-stage-content">{content}</div>
</div>
);
}
}

View File

@ -0,0 +1,2 @@
export * from './settingField';
export * from './fields';

View File

@ -0,0 +1,30 @@
import { Component } from 'react';
export interface InlineTipProps {
position: string;
theme?: 'green' | 'black';
children: React.ReactNode;
}
export default class InlineTip extends Component<InlineTipProps> {
public static displayName = 'InlineTip';
public static defaultProps = {
position: 'auto',
theme: 'black',
};
public 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>
);
}
}

View File

@ -0,0 +1,186 @@
import VariableSetter from './variableSetter';
import context from '../context';
import { VE_HOOKS } from '../base/const';
import {
AccordionField,
BlockField,
EntryField,
InlineField,
PlainField,
PopupField
} from "./fields";
import { ComponentClass, Component, isValidElement, createElement } from 'react';
import { createSetterContent, getSetter } from '@ali/lowcode-editor-core';
function isReactClass(obj: any): obj is ComponentClass<any> {
return (
obj &&
obj.prototype &&
(obj.prototype.isReactComponent || obj.prototype instanceof Component)
);
}
interface IExtraProps {
stageName?: string;
isGroup?: boolean;
isExpand?: boolean;
propName?: string;
toggleExpand?: () => any;
onExpandChange?: () => any;
}
const FIELD_TYPE_MAP: any = {
accordion: AccordionField,
block: BlockField,
entry: EntryField,
inline: InlineField,
plain: PlainField,
popup: PopupField,
tab: AccordionField
};
export class SettingField extends Component {
public readonly props: {
prop: any;
selected?: boolean;
forceDisplay?: string;
className?: string;
children?: JSX.Element | string;
compact?: boolean;
key?: string;
addonProps?: object;
};
/**
* VariableSetter placeholder
*/
public variableSetter: any;
constructor(props: any) {
super(props);
this.variableSetter = getSetter('VariableSetter')?.component || VariableSetter;
}
public render() {
const { prop, selected, addonProps } = this.props;
const display = this.props.forceDisplay || prop.getDisplay();
if (display === "none") {
return null;
}
// 标准的属性,即每一个 Field 在 VE 下都拥有的属性
const standardProps = {
className: this.props.className,
compact: this.props.compact,
isSupportMultiSetter: this.supportMultiSetter(),
isSupportVariable: prop.isSupportVariable(),
isUseVariable: prop.isUseVariable(),
prop,
setUseVariable: () => prop.setUseVariable(!prop.isUseVariable()),
tip: prop.getTip(),
title: prop.getTitle()
};
// 部分 Field 所需要的额外 fieldProps
const extraProps = {};
const ctx = context;
const plugin = ctx.getPlugin(VE_HOOKS.VE_SETTING_FIELD_PROVIDER);
let Field;
if (typeof plugin === "function") {
Field = plugin(display, FIELD_TYPE_MAP, prop);
}
if (!Field) {
Field = FIELD_TYPE_MAP[display] || PlainField;
}
this._prepareProps(display, extraProps);
if (display === "entry") {
return <Field {...{ ...standardProps, ...extraProps }} />;
}
let setter;
const props: any = {
prop,
selected,
};
const fieldProps = { ...standardProps, ...extraProps };
if (prop.isUseVariable() && !this.variableSetter.isPopup) {
props.placeholder = "请输入表达式: ${var}";
props.key = `${prop.getId()}-variable`;
setter = createElement(this.variableSetter, props);
return <Field {...fieldProps}>{setter}</Field>;
}
// for composited prop
if (prop.getVisibleItems) {
setter = prop
.getVisibleItems()
.map((item: any) => (
<SettingField {...{ key: item.getId(), prop: item, selected }} />
));
return <Field {...fieldProps}>{setter}</Field>;
}
setter = prop.getSetter();
if (
typeof setter === "object" &&
"componentName" in setter &&
!(isValidElement(setter) || isReactClass(setter))
) {
const { componentName: setterType, props: setterProps } = setter as any;
setter = createSetterContent(setterType, {
...addonProps,
...setterProps,
...props
});
} else {
setter = createSetterContent(setter, {
...addonProps,
...props
});
}
return <Field {...fieldProps}>{setter}</Field>;
}
private supportMultiSetter() {
const { prop } = this.props;
const setter = prop && prop.getConfig && prop.getConfig("setter");
return prop.isSupportVariable() || Array.isArray(setter);
}
private _prepareProps(displayType: string, extraProps: IExtraProps): void {
const { prop } = this.props;
extraProps.propName = prop.isGroup()
? "组合属性,无属性名称"
: prop.getName();
switch (displayType) {
case "title":
break;
case "block":
Object.assign(extraProps, { isGroup: prop.isGroup() });
break;
case "accordion":
Object.assign(extraProps, {
headDIY: true,
isExpand: prop.isExpand(),
isGroup: prop.isGroup(),
onExpandChange: () => prop.onExpandChange(() => this.forceUpdate()),
toggleExpand: () => {
prop.toggleExpand();
}
});
break;
case "entry":
Object.assign(extraProps, { stageName: prop.getName() });
break;
default:
break;
}
}
}

View File

@ -0,0 +1,43 @@
@import '~@ali/ve-less-variables/index.less';
.engine-input-control {
box-sizing: border-box;
font-size: 12px;
font-family: Consolas, "Courier New", Courier, FreeMono, monospace;
color: var(--color-text, @dark-alpha-3);
background: var(--color-field-background, @white-alpha-1);
border: 1px solid var(--color-field-border, @normal-alpha-5);
flex: 1;
border-radius: @global-border-radius;
max-height: 200px;
&:hover {
border-color: var(--color-field-border-hover, @normal-alpha-4);
}
&.engine-focused {
border-color: var(--color-field-border-active, @normal-alpha-3);
}
textarea {
resize: none;
}
>.engine-input {
box-sizing: border-box;
padding: 6px;
display: block;
font-size: 12px;
line-height: 16px;
color: var(--color-text, @dark-alpha-3);
width: 100%;
border: 0;
margin: 0;
background: transparent;
outline: none;
&::-webkit-input-placeholder {
color: var(--color-field-placeholder, @normal-alpha-5);
}
}
}

View File

@ -0,0 +1,85 @@
import './variableSetter.less';
import { Component } from 'react';
class Input extends Component {
public props: {
value: string;
placeholder: string;
onChange: (val: any) => any;
};
public state: { focused: boolean };
constructor(props: object) {
super(props);
this.state = {
focused: false,
};
}
public componentDidMount() {
this.adjustTextAreaHeight();
}
private domRef: HTMLTextAreaElement | null = null;
public adjustTextAreaHeight() {
if (!this.domRef) {
return;
}
this.domRef.style.height = '1px';
const calculatedHeight = this.domRef.scrollHeight;
this.domRef.style.height = calculatedHeight >= 200 ? '200px' : calculatedHeight + 'px';
}
public render() {
const { value, placeholder, onChange } = this.props;
return (
<div
className={`engine-variable-setter-input engine-input-control${this.state.focused ? ' engine-focused' : ''}`}
>
<textarea
ref={(r) => {
this.domRef = r;
}}
className="engine-input"
value={value || ''}
placeholder={placeholder || ''}
onChange={(e) => {
onChange(e.target.value || '');
}}
onBlur={() => this.setState({ focused: false })}
onFocus={() => this.setState({ focused: true })}
onKeyUp={this.adjustTextAreaHeight.bind(this)}
></textarea>
</div>
);
}
}
export default class VariableSetter extends Component<{
prop: any;
placeholder: string;
}> {
public willDetach: () => any;
public componentWillMount() {
this.willDetach = this.props.prop.onValueChange(() => this.forceUpdate());
}
public componentWillUnmount() {
if (this.willDetach) {
this.willDetach();
}
}
public render() {
const prop = this.props.prop;
return (
<Input
value={prop.getVariableValue()}
placeholder={this.props.placeholder}
onChange={(val: string) => prop.setVariableValue(val)}
/>
);
}
}

View File

@ -0,0 +1,20 @@
@import '~@ali/ve-less-variables/index.less';
.engine-field-variable-switcher {
cursor: pointer;
opacity: 0.6;
margin-left: 2px;
&.engine-active {
opacity: 1;
background: var(--color-brand, @brand-color-1);
color: #fff !important;
border-radius: 3px;
margin-left: 4px;
svg {
height: 22px !important;
width: 22px !important;
}
}
}

View File

@ -0,0 +1,57 @@
import VariableSetter from './variableSetter';
import Icons from '@ali/ve-icons';
import { IVEFieldProps } from './field';
import './variableSwitcher.less';
import { Component } from 'react';
import { getSetter } from '@ali/lowcode-editor-core';
interface IState {
visible: boolean;
}
export default class VariableSwitcher extends Component<IVEFieldProps, IState> {
private ref: HTMLElement | null = null;
private VariableSetter: any;
constructor(props: IVEFieldProps) {
super(props);
this.VariableSetter = getSetter('VariableSetter')?.component || VariableSetter;
this.state = {
visible: false,
};
}
public render() {
const { isUseVariable, prop } = this.props;
const { visible } = this.state;
const isSupportVariable = prop.isSupportVariable();
const tip = !isUseVariable ? '绑定变量' : prop.getVariableValue();
if (!isSupportVariable) {
return null;
}
return (
<div>
<Icons.Tip
name='var'
size='24px'
position='bottom center'
className={`engine-field-variable-switcher ${isUseVariable ? 'engine-active' : ''}`}
data-tip={tip}
onClick={(e: Event) => {
e.stopPropagation();
if (this.VariableSetter.isPopup) {
this.VariableSetter.show({
prop,
});
} else {
prop.setUseVariable(!isUseVariable);
}
}}>
</Icons.Tip>
</div>
);
}
}

View File

@ -17,7 +17,7 @@ import Trunk from './bundle/trunk';
import Prototype from './bundle/prototype';
import Bundle from './bundle/bundle';
import Pages from './pages';
import Field from './field';
import * as Field from './fields';
import Prop from './prop';
import Env from './env';
import DragEngine from './drag-engine';