mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-03-01 05:30:40 +00:00
377 lines
9.5 KiB
TypeScript
377 lines
9.5 KiB
TypeScript
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>
|
||
);
|
||
}
|
||
}
|