import { Component, MouseEvent, Fragment } from 'react'; import { shallowIntl, observer, obx, engineConfig, runInAction, globalContext } from '@alilc/lowcode-editor-core'; import { createContent, isJSSlot, isSetterConfig } from '@alilc/lowcode-utils'; import { Skeleton, Stage } from '@alilc/lowcode-editor-skeleton'; import { IPublicTypeCustomView } from '@alilc/lowcode-types'; import { SettingField, SettingTopEntry, ISettingEntry, IComponentMeta, ISettingField, isSettingField } from '@alilc/lowcode-designer'; import { createField } from '../field'; import PopupService, { PopupPipe } from '../popup'; import { SkeletonContext } from '../../context'; import { intl } from '../../locale'; import { Setters } from '@alilc/lowcode-shell'; function isStandardComponent(componentMeta: IComponentMeta | null) { if (!componentMeta) return false; const { prototype } = componentMeta; return prototype == null; } /** * 判断 initialValue 是否为非空,非空条件: * 1. 当为 slot 结构时,value 为有长度的数组且 visible 不为 false * 2. 不为 slot 结构,为非 undefined / null 值 * @param initialValue * @returns */ function isInitialValueNotEmpty(initialValue: any) { if (isJSSlot(initialValue)) { // @ts-ignore visible 为 false 代表默认不展示 return initialValue.visible !== false && Array.isArray(initialValue.value) && initialValue.value.length > 0; } return (initialValue !== undefined && initialValue !== null); } type SettingFieldViewProps = { field: ISettingField }; type SettingFieldViewState = { fromOnChange: boolean; value: any }; @observer class SettingFieldView extends Component { static contextType = SkeletonContext; stageName: string | undefined; setters: Setters; constructor(props: SettingFieldViewProps) { super(props); const { field } = this.props; const { extraProps } = field; const { display } = extraProps; const workspace = globalContext.get('workspace'); const editor = workspace.isActive ? workspace.window.editor : globalContext.get('editor'); const { stages } = editor.get('skeleton') as Skeleton; this.setters = editor.get('setters'); let stageName; if (display === 'entry') { runInAction(() => { stageName = `${field.getNode().id}_${field.name?.toString()}`; // 清除原 stage,不然 content 引用的一直是老的 field,导致数据无法得到更新 stages.container.remove(stageName); stages.add({ type: 'Widget', name: stageName, content: {field.items.map((item, index) => createSettingFieldView(item, field, index))}, props: { title: field.title, }, }); }); } this.stageName = stageName; } get field() { return this.props.field; } get visible() { const { extraProps } = this.field; const { condition } = extraProps; try { return typeof condition === 'function' ? condition(this.field.internalToShellPropEntry()) !== false : true; } catch (error) { console.error('exception when condition (hidden) is excuted', error); } return true; } get setterInfo(): { setterProps: any; initialValue: any; setterType: any; } { const { extraProps, componentMeta } = this.field; const { defaultValue } = extraProps; const { setter } = this.field; let setterProps: any = {}; let setterType: any; let initialValue: any = null; if (Array.isArray(setter)) { setterType = 'MixedSetter'; setterProps = { setters: setter, }; } else if (isSetterConfig(setter)) { setterType = setter.componentName; if (setter.props) { setterProps = setter.props; if (typeof setterProps === 'function') { setterProps = setterProps(this.field.internalToShellPropEntry()); } } if (setter.initialValue != null) { initialValue = setter.initialValue; } } else if (setter) { setterType = setter; } if (defaultValue != null && !('defaultValue' in setterProps)) { setterProps.defaultValue = defaultValue; if (initialValue == null) { initialValue = defaultValue; } } if (this.field.valueState === -1) { setterProps.multiValue = true; if (!('placeholder' in setterProps)) { setterProps.placeholder = intl('Multiple Value'); } } // 根据是否支持变量配置做相应的更改 const supportVariable = this.field.extraProps?.supportVariable; // supportVariableGlobally 只对标准组件生效,vc 需要单独配置 const supportVariableGlobally = engineConfig.get('supportVariableGlobally', false) && isStandardComponent(componentMeta); if (supportVariable || supportVariableGlobally) { if (setterType === 'MixedSetter') { // VariableSetter 不单独使用 if (Array.isArray(setterProps.setters) && !setterProps.setters.includes('VariableSetter')) { setterProps.setters.push('VariableSetter'); } } else { setterType = 'MixedSetter'; setterProps = { setters: [ setter, 'VariableSetter', ], }; } } return { setterProps, initialValue, setterType, }; } get value() { return this.field.valueState === -1 ? null : this.field.getValue(); } initDefaultValue() { const { initialValue } = this.setterInfo; if (this.state?.fromOnChange || !isInitialValueNotEmpty(initialValue) || this.value !== undefined ) { return; } // 当前 field 没有 value 值时,将 initialValue 写入 field // 之所以用 initialValue,而不是 defaultValue 是为了保持跟 props.onInitial 的逻辑一致 const _initialValue = typeof initialValue === 'function' ? initialValue(this.field.internalToShellPropEntry()) : initialValue; this.field.setValue(_initialValue); } componentDidMount() { this.initDefaultValue(); } render() { const field = this.field; const { extraProps } = field; const visible = this.visible; if (!visible) { return null; } const { setterProps = {}, setterType, initialValue = null, } = this.setterInfo; const value = this.value; let _onChange = extraProps?.onChange; let stageName = this.stageName; return createField( { meta: field?.componentMeta?.npm || field?.componentMeta?.componentName || '', title: field.title, // editor: field.editor, collapsed: !field.expanded, valueState: field.isRequired ? 10 : field.valueState, onExpandChange: (expandState) => field.setExpanded(expandState), onClear: () => field.clearValue(), // field: field, // stages, stageName, ...extraProps, }, !stageName && this.setters.createSetterContent(setterType, { ...shallowIntl(setterProps), forceInline: extraProps.forceInline, key: field.id, // === injection prop: field.internalToShellPropEntry(), // for compatible vision selected: field.top?.getNode()?.internalToShellNode(), field: field.internalToShellPropEntry(), // === IO value, // reaction point initialValue, onChange: (value: any) => { this.setState({ fromOnChange: true, // eslint-disable-next-line react/no-unused-state value, }); field.setValue(value, true); if (_onChange) _onChange(value, field); }, onInitial: () => { if (initialValue == null) { return; } const value = typeof initialValue === 'function' ? initialValue(field.internalToShellPropEntry()) : initialValue; this.setState({ // eslint-disable-next-line react/no-unused-state value, }); field.setValue(value, true); }, removeProp: () => { if (field.name) { field.parent.clearPropValue(field.name); } }, }), extraProps.forceInline ? 'plain' : extraProps.display, ); } } type SettingGroupViewProps = SettingFieldViewProps; @observer class SettingGroupView extends Component { static contextType = SkeletonContext; stageName: string | undefined; constructor(props: SettingGroupViewProps) { super(props); const { field } = this.props; const { extraProps } = field; const { display } = extraProps; const workspace = globalContext.get('workspace'); const editor = workspace.isActive ? workspace.window.editor : globalContext.get('editor'); const { stages } = editor.get('skeleton') as Skeleton; // const items = field.items; let stageName; if (display === 'entry') { runInAction(() => { stageName = `${field.getNode().id}_${field.name?.toString()}`; // 清除原 stage,不然 content 引用的一直是老的 field,导致数据无法得到更新 stages.container.remove(stageName); stages.add({ type: 'Widget', name: stageName, content: {field.items.map((item, index) => createSettingFieldView(item, field, index))}, props: { title: field.title, }, }); }); } this.stageName = stageName; } render() { const { field } = this.props; const { extraProps } = field; const { condition, display } = extraProps; const visible = field.isSingle && typeof condition === 'function' ? condition(field.internalToShellPropEntry()) !== false : true; if (!visible) { return null; } // todo: split collapsed state | field.items for optimize return createField( { meta: field.componentMeta?.npm || field.componentMeta?.componentName || '', title: field.title, // editor: field.editor, collapsed: !field.expanded, onExpandChange: (expandState) => field.setExpanded(expandState), // field: field, // stages, stageName: this.stageName, }, field.items.map((item, index) => createSettingFieldView(item, field, index)), display, ); } } export function createSettingFieldView(item: ISettingField | IPublicTypeCustomView, field: ISettingEntry, index?: number) { if (isSettingField(item)) { if (item.isGroup) { return ; } else { return ; } } else { return createContent(item, { key: index, field }); } } export type SettingsPaneProps = { target: SettingTopEntry | SettingField; usePopup?: boolean; }; @observer export class SettingsPane extends Component { static contextType = SkeletonContext; @obx private currentStage?: Stage; private popupPipe = new PopupPipe(); private pipe = this.popupPipe.create(); private handleClick = (e: MouseEvent) => { // compatiable vision stageBox // TODO: optimize these codes const { usePopup = true } = this.props; if (!usePopup) return; const pane = e.currentTarget as HTMLDivElement; function getTarget(node: any): any { if (!pane.contains(node) || (node.nodeName === 'A' && node.getAttribute('href'))) { return null; } const target = node.dataset ? node.dataset.stageTarget : null; if (target) { return target; } return getTarget(node.parentNode); } const target = getTarget(e.target); if (!target) { return; } const skeleton = this.context as Skeleton; if (!skeleton || !skeleton.stages) { return; } const stage = skeleton.stages.container.get(target); if (stage) { if (this.currentStage) { stage.setPrevious(this.currentStage); } this.currentStage = stage; } }; private popStage() { this.currentStage = this.currentStage?.getPrevious(); } render() { const { target } = this.props; const { items } = target; return (
{/* todo: add head for single use */}
{items.map((item, index) => createSettingFieldView(item, target, index))}
); } }