mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-02-28 04:40:32 +00:00
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:
commit
550fdde7f7
@ -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",
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
19
packages/demo/src/vision/module.d.ts
vendored
19
packages/demo/src/vision/module.d.ts
vendored
@ -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';
|
||||
|
||||
@ -465,6 +465,14 @@ export class DocumentModel {
|
||||
getRoot() {
|
||||
return this.rootNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容vision
|
||||
*/
|
||||
getHistory(): History {
|
||||
return this.history;
|
||||
}
|
||||
|
||||
get root() {
|
||||
return this.rootNode;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
150
packages/vision-preset/src/fields/field.tsx
Normal file
150
packages/vision-preset/src/fields/field.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
272
packages/vision-preset/src/fields/fields.less
Normal file
272
packages/vision-preset/src/fields/fields.less
Normal 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;
|
||||
}
|
||||
376
packages/vision-preset/src/fields/fields.tsx
Normal file
376
packages/vision-preset/src/fields/fields.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
2
packages/vision-preset/src/fields/index.ts
Normal file
2
packages/vision-preset/src/fields/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './settingField';
|
||||
export * from './fields';
|
||||
30
packages/vision-preset/src/fields/inlinetip.tsx
Normal file
30
packages/vision-preset/src/fields/inlinetip.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
186
packages/vision-preset/src/fields/settingField.tsx
Normal file
186
packages/vision-preset/src/fields/settingField.tsx
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
packages/vision-preset/src/fields/variableSetter.less
Normal file
43
packages/vision-preset/src/fields/variableSetter.less
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
85
packages/vision-preset/src/fields/variableSetter.tsx
Normal file
85
packages/vision-preset/src/fields/variableSetter.tsx
Normal 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)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
20
packages/vision-preset/src/fields/variableSwitcher.less
Normal file
20
packages/vision-preset/src/fields/variableSwitcher.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
packages/vision-preset/src/fields/variableSwitcher.tsx
Normal file
57
packages/vision-preset/src/fields/variableSwitcher.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user