2022-02-18 19:48:57 +08:00

359 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Component, MouseEvent, Fragment } from 'react';
import { shallowIntl, createSetterContent, observer, obx, engineConfig, runInAction, globalContext } from '@alilc/lowcode-editor-core';
import { createContent } from '@alilc/lowcode-utils';
import { Skeleton } from '@alilc/lowcode-editor-skeleton';
import { isSetterConfig, CustomView, isJSSlot } from '@alilc/lowcode-types';
import { SettingField, isSettingField, SettingTopEntry, SettingEntry, ComponentMeta } from '@alilc/lowcode-designer';
import { createField } from '../field';
import PopupService, { PopupPipe } from '../popup';
import { SkeletonContext } from '../../context';
// import { Icon } from '@alifd/next';
import { intl } from '../../locale';
function isStandardComponent(componentMeta: ComponentMeta | 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: SettingField };
type SettingFieldViewState = { fromOnChange: boolean; value: any };
@observer
class SettingFieldView extends Component<SettingFieldViewProps, SettingFieldViewState> {
static contextType = SkeletonContext;
stageName: string | undefined;
constructor(props: SettingFieldViewProps) {
super(props);
const { field } = this.props;
const { extraProps } = field;
const { display } = extraProps;
const editor = globalContext.get('editor');
const { stages } = editor.get('skeleton') as Skeleton;
let stageName;
if (display === 'entry') {
runInAction(() => {
stageName = `${field.getNode().id}_${field.name.toString()}`;
// 清除原 stage不然 content 引用的一直是老的 field导致数据无法得到更新
stages.container.remove(stageName);
const stage = stages.add({
type: 'Widget',
name: stageName,
content: <Fragment>{field.items.map((item, index) => createSettingFieldView(item, field, index))}</Fragment>,
props: {
title: field.title,
},
});
});
}
this.stageName = stageName;
}
render() {
const { field } = this.props;
const { extraProps, componentMeta } = field;
const { condition, defaultValue } = extraProps;
let visible;
try {
visible = typeof condition === 'function' ? condition(field.internalToShellPropEntry()) !== false : true;
} catch (error) {
console.error('exception when condition (hidden) is excuted', error);
}
if (!visible) {
return null;
}
const { setter } = 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(field.internalToShellPropEntry());
}
}
if (setter.initialValue != null) {
initialValue = setter.initialValue;
}
} else if (setter) {
setterType = setter;
}
// 根据是否支持变量配置做相应的更改
const supportVariable = 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',
],
};
}
}
let value = null;
if (defaultValue != null && !('defaultValue' in setterProps)) {
setterProps.defaultValue = defaultValue;
if (initialValue == null) {
initialValue = defaultValue;
}
}
if (field.valueState === -1) {
setterProps.multiValue = true;
if (!('placeholder' in setterProps)) {
setterProps.placeholder = intl('Multiple Value');
}
} else {
value = field.getValue();
}
// 当前 field 没有 value 值时,将 initialValue 写入 field
// 之所以用 initialValue而不是 defaultValue 是为了保持跟 props.onInitial 的逻辑一致
if (!this.state?.fromOnChange && value === undefined && isInitialValueNotEmpty(initialValue)) {
const _initialValue = typeof initialValue === 'function' ? initialValue(field.internalToShellPropEntry()) : initialValue;
field.setValue(_initialValue);
value = _initialValue;
}
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 &&
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);
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);
},
removeProp: () => {
field.parent.clearPropValue(field.name);
},
}),
extraProps.forceInline ? 'plain' : extraProps.display,
);
}
}
type SettingGroupViewProps = SettingFieldViewProps;
@observer
class SettingGroupView extends Component<SettingGroupViewProps> {
static contextType = SkeletonContext;
stageName: string | undefined;
constructor(props: SettingGroupViewProps) {
super(props);
const { field } = this.props;
const { extraProps } = field;
const { display } = extraProps;
const 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: <Fragment>{field.items.map((item, index) => createSettingFieldView(item, field, index))}</Fragment>,
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: SettingField | CustomView, field: SettingEntry, index?: number) {
if (isSettingField(item)) {
if (item.isGroup) {
return <SettingGroupView field={item} key={item.id} />;
} else {
return <SettingFieldView field={item} key={item.id} />;
}
} else {
return createContent(item, { key: index, field });
}
}
export type SettingsPaneProps = {
target: SettingTopEntry | SettingField;
usePopup?: boolean;
};
@observer
export class SettingsPane extends Component<SettingsPaneProps> {
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 (
<div className="lc-settings-pane" onClick={this.handleClick}>
{/* todo: add head for single use */}
<PopupService popupPipe={this.popupPipe}>
<div className="lc-settings-content">
{items.map((item, index) => createSettingFieldView(item, target, index))}
</div>
</PopupService>
</div>
);
}
}