From 853a68c3f7a9298da7c3ca0a364a8f255e6235e9 Mon Sep 17 00:00:00 2001 From: kangwei Date: Mon, 4 May 2020 14:54:05 +0800 Subject: [PATCH] migrate old fields for out use --- packages/vision-preset/src/field.tsx | 155 -------- packages/vision-preset/src/fields/field.tsx | 150 +++++++ packages/vision-preset/src/fields/fields.less | 272 +++++++++++++ packages/vision-preset/src/fields/fields.tsx | 376 ++++++++++++++++++ packages/vision-preset/src/fields/index.ts | 2 + .../vision-preset/src/fields/inlinetip.tsx | 30 ++ .../vision-preset/src/fields/settingField.tsx | 186 +++++++++ .../src/fields/variableSetter.less | 43 ++ .../src/fields/variableSetter.tsx | 85 ++++ .../src/fields/variableSwitcher.less | 20 + .../src/fields/variableSwitcher.tsx | 57 +++ packages/vision-preset/src/index.ts | 2 +- 12 files changed, 1222 insertions(+), 156 deletions(-) delete mode 100644 packages/vision-preset/src/field.tsx create mode 100644 packages/vision-preset/src/fields/field.tsx create mode 100644 packages/vision-preset/src/fields/fields.less create mode 100644 packages/vision-preset/src/fields/fields.tsx create mode 100644 packages/vision-preset/src/fields/index.ts create mode 100644 packages/vision-preset/src/fields/inlinetip.tsx create mode 100644 packages/vision-preset/src/fields/settingField.tsx create mode 100644 packages/vision-preset/src/fields/variableSetter.less create mode 100644 packages/vision-preset/src/fields/variableSetter.tsx create mode 100644 packages/vision-preset/src/fields/variableSwitcher.less create mode 100644 packages/vision-preset/src/fields/variableSwitcher.tsx diff --git a/packages/vision-preset/src/field.tsx b/packages/vision-preset/src/field.tsx deleted file mode 100644 index 346ed36b1..000000000 --- a/packages/vision-preset/src/field.tsx +++ /dev/null @@ -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 ; - } - - 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 {setter}; - } - - // for composited prop - if (prop.getVisibleItems) { - setter = prop - .getVisibleItems() - .map((item: any) => ); - return {setter}; - } - - setter = createSetterContent(prop.getSetter(), { - ...addonProps, - ...props, - }); - - return {setter}; - } - - 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; diff --git a/packages/vision-preset/src/fields/field.tsx b/packages/vision-preset/src/fields/field.tsx new file mode 100644 index 000000000..3fc13ea7e --- /dev/null +++ b/packages/vision-preset/src/fields/field.tsx @@ -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) => {w}); +} + +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 = ( +
+
属性:{propName}
+
+ ); + + if (isPlainObject(tip)) { + tipUrl = tip.url; + tipContent = ( +
+
属性:{propName}
+
说明:{tip.content}
+
+ ); + } else if (tip) { + tipContent = ( +
+
属性:{propName}
+
说明:{tip}
+
+ ); + } + return ( + + {titleContent || (typeof title === 'object' ? '' : title)} + {tipContent} + + ); +} + +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 { + 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 ( +
Field render error, please open console to find out.
+ ); + } + + const headContent = headDIY ? this.renderHead() + :
{this.renderHead()}
; + + return ( +
+ {headContent} +
+ {this.renderBody()} +
+
+ {this.renderFoot()} +
+
+ ); + } +} diff --git a/packages/vision-preset/src/fields/fields.less b/packages/vision-preset/src/fields/fields.less new file mode 100644 index 000000000..c02417425 --- /dev/null +++ b/packages/vision-preset/src/fields/fields.less @@ -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; +} diff --git a/packages/vision-preset/src/fields/fields.tsx b/packages/vision-preset/src/fields/fields.tsx new file mode 100644 index 000000000..e106fe7f7 --- /dev/null +++ b/packages/vision-preset/src/fields/fields.tsx @@ -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 ( + +
+
{propName}
+
+
+ ); + } + if (testType(tip) === 'object') { + return ( + +
+
属性:{propName}
+
说明:{tip.content}
+
+
+ ); + } + return ( + +
+
属性:{propName}
+
说明:{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 ( +
+ +
+ ); + } +} + +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 [ + + {title} + , + renderTip(tip, { propName }), + , + ]; + } +} + +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 ( +
toggleExpand && toggleExpand()}> + + {title} + {renderTip(tip, { propName })} + {} +
+ ); + } + + 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 = [ + + {title} + , + renderTip(tip, { propName }), + , + ]; + + return ( +
+ {innerElements} +
+ ); + } +} + +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 ( +
+ popups.popup({ + cancelOnBlur: true, + content: this.props.children, + position: 'left bottom', + showClose: true, + sizeFixed: true, + target: e.currentTarget, + }) + } + > + {title} + {renderTip(tip, { propName })} + + +
+ ); + } +} + +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 ( +
+ {title} + {renderTip(tip, { propName })} +
+ ); + } +} + +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 = ( +
+ {stage.getTabs().map((tab: any) => ( +
stage.setCurrentTab(tab)} + > + {tab.getTitle()} + {renderTip(tab.getTip())} +
+ ))} +
+ ); + } + + if (currentTab) { + if (currentTab.getVisibleItems) { + content = currentTab + .getVisibleItems() + .map((item: any) => ); + } else if (currentTab.getSetter) { + content = ( + + ); + } + } + } 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 = ( +
+ + {stage.getTitle()} + {renderTip(stage.getTip())} +
+ ); + } + + return ( +
{ + this.shell = ref; + }} + className={className} + > + {stageBacker} + {tabs} +
{content}
+
+ ); + } +} diff --git a/packages/vision-preset/src/fields/index.ts b/packages/vision-preset/src/fields/index.ts new file mode 100644 index 000000000..682cba49d --- /dev/null +++ b/packages/vision-preset/src/fields/index.ts @@ -0,0 +1,2 @@ +export * from './settingField'; +export * from './fields'; diff --git a/packages/vision-preset/src/fields/inlinetip.tsx b/packages/vision-preset/src/fields/inlinetip.tsx new file mode 100644 index 000000000..f3344b1c3 --- /dev/null +++ b/packages/vision-preset/src/fields/inlinetip.tsx @@ -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 { + public static displayName = 'InlineTip'; + + public static defaultProps = { + position: 'auto', + theme: 'black', + }; + + public render(): React.ReactNode { + const { position, theme, children } = this.props; + return ( +
+ {children} +
+ ); + } +} diff --git a/packages/vision-preset/src/fields/settingField.tsx b/packages/vision-preset/src/fields/settingField.tsx new file mode 100644 index 000000000..4edd0c52d --- /dev/null +++ b/packages/vision-preset/src/fields/settingField.tsx @@ -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 { + 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 ; + } + + 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 {setter}; + } + + // for composited prop + if (prop.getVisibleItems) { + setter = prop + .getVisibleItems() + .map((item: any) => ( + + )); + return {setter}; + } + + 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 {setter}; + } + + 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; + } + } +} diff --git a/packages/vision-preset/src/fields/variableSetter.less b/packages/vision-preset/src/fields/variableSetter.less new file mode 100644 index 000000000..6ea5653d3 --- /dev/null +++ b/packages/vision-preset/src/fields/variableSetter.less @@ -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); + } + } +} diff --git a/packages/vision-preset/src/fields/variableSetter.tsx b/packages/vision-preset/src/fields/variableSetter.tsx new file mode 100644 index 000000000..51643bd16 --- /dev/null +++ b/packages/vision-preset/src/fields/variableSetter.tsx @@ -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 ( +
+ +
+ ); + } +} + +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 ( + prop.setVariableValue(val)} + /> + ); + } +} diff --git a/packages/vision-preset/src/fields/variableSwitcher.less b/packages/vision-preset/src/fields/variableSwitcher.less new file mode 100644 index 000000000..0991b0ed6 --- /dev/null +++ b/packages/vision-preset/src/fields/variableSwitcher.less @@ -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; + } + } +} diff --git a/packages/vision-preset/src/fields/variableSwitcher.tsx b/packages/vision-preset/src/fields/variableSwitcher.tsx new file mode 100644 index 000000000..146ad7502 --- /dev/null +++ b/packages/vision-preset/src/fields/variableSwitcher.tsx @@ -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 { + 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 ( +
+ { + e.stopPropagation(); + if (this.VariableSetter.isPopup) { + this.VariableSetter.show({ + prop, + }); + } else { + prop.setUseVariable(!isUseVariable); + } + }}> + 绑定变量 + +
+ ); + } +} diff --git a/packages/vision-preset/src/index.ts b/packages/vision-preset/src/index.ts index 31185f2d2..3015995cb 100644 --- a/packages/vision-preset/src/index.ts +++ b/packages/vision-preset/src/index.ts @@ -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';