mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-16 06:42:53 +00:00
280 lines
7.6 KiB
TypeScript
280 lines
7.6 KiB
TypeScript
import { Component, Fragment } from 'react';
|
||
import { Icon, Button, Message } from '@alifd/next';
|
||
import Sortable from './sortable';
|
||
import { SettingField, SetterType, FieldConfig } from '../../main';
|
||
import './style.less';
|
||
import { createSettingFieldView } from '../../settings-pane';
|
||
import { PopupContext, PopupPipe } from '../../popup';
|
||
import Title from '../../title';
|
||
|
||
interface ArraySetterState {
|
||
items: SettingField[];
|
||
itemsMap: Map<string | number, SettingField>;
|
||
prevLength: number;
|
||
}
|
||
|
||
interface ArraySetterProps {
|
||
value: any[];
|
||
field: SettingField;
|
||
itemConfig?: {
|
||
setter?: SetterType;
|
||
defaultValue?: any | ((field: SettingField) => any);
|
||
required?: boolean;
|
||
};
|
||
multiValue?: boolean;
|
||
}
|
||
|
||
export class ListSetter extends Component<ArraySetterProps, ArraySetterState> {
|
||
static getDerivedStateFromProps(props: ArraySetterProps, state: ArraySetterState) {
|
||
const { value, field } = props;
|
||
const newLength = value && Array.isArray(value) ? value.length : 0;
|
||
if (state && state.prevLength === newLength) {
|
||
return null;
|
||
}
|
||
|
||
// props value length change will go here
|
||
const originLength = state ? state.items.length : 0;
|
||
if (state && originLength === newLength) {
|
||
return {
|
||
prevLength: newLength,
|
||
};
|
||
}
|
||
|
||
const itemsMap = state ? state.itemsMap : new Map<string | number, SettingField>();
|
||
let items = state ? state.items.slice() : [];
|
||
if (newLength > originLength) {
|
||
for (let i = originLength; i < newLength; i++) {
|
||
const item = field.createField({
|
||
...props.itemConfig,
|
||
name: i,
|
||
forceInline: 2,
|
||
});
|
||
items[i] = item;
|
||
itemsMap.set(item.id, item);
|
||
}
|
||
} else if (newLength < originLength) {
|
||
const deletes = items.splice(newLength);
|
||
deletes.forEach(item => {
|
||
itemsMap.delete(item.id);
|
||
});
|
||
}
|
||
return {
|
||
items,
|
||
itemsMap,
|
||
prevLength: newLength,
|
||
};
|
||
}
|
||
|
||
state: ArraySetterState = {
|
||
items: [],
|
||
itemsMap: new Map<string | number, SettingField>(),
|
||
prevLength: 0,
|
||
};
|
||
|
||
onSort(sortedIds: Array<string | number>) {
|
||
const { itemsMap } = this.state;
|
||
const items = sortedIds.map((id, index) => {
|
||
const item = itemsMap.get(id)!;
|
||
item.setKey(index);
|
||
return item;
|
||
});
|
||
this.setState({
|
||
items,
|
||
});
|
||
}
|
||
|
||
private scrollToLast: boolean = false;
|
||
onAdd() {
|
||
const { items, itemsMap } = this.state;
|
||
const { itemConfig } = this.props;
|
||
const defaultValue = itemConfig ? itemConfig.defaultValue : null;
|
||
const item = this.props.field.createField({
|
||
...itemConfig,
|
||
name: items.length,
|
||
forceInline: 1,
|
||
});
|
||
items.push(item);
|
||
itemsMap.set(item.id, item);
|
||
item.setValue(typeof defaultValue === 'function' ? defaultValue(item) : defaultValue);
|
||
this.scrollToLast = true;
|
||
this.setState({
|
||
items: items.slice(),
|
||
});
|
||
}
|
||
|
||
onRemove(field: SettingField) {
|
||
const { items } = this.state;
|
||
let i = items.indexOf(field);
|
||
if (i < 0) {
|
||
return;
|
||
}
|
||
items.splice(i, 1);
|
||
const l = items.length;
|
||
while (i < l) {
|
||
items[i].setKey(i);
|
||
i++;
|
||
}
|
||
field.remove();
|
||
this.setState({ items: items.slice() });
|
||
}
|
||
|
||
componentWillUnmount() {
|
||
this.state.items.forEach(field => {
|
||
field.purge();
|
||
});
|
||
}
|
||
|
||
shouldComponentUpdate(_: any, nextState: ArraySetterState) {
|
||
if (nextState.items !== this.state.items) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
render() {
|
||
// mini Button: depends popup
|
||
if (this.props.itemConfig) {
|
||
// check is ObjectSetter then check if show columns
|
||
}
|
||
|
||
const { items } = this.state;
|
||
const scrollToLast = this.scrollToLast;
|
||
this.scrollToLast = false;
|
||
const lastIndex = items.length - 1;
|
||
|
||
const content =
|
||
items.length > 0 ? (
|
||
<div className="lc-setter-list-scroll-body">
|
||
<Sortable itemClassName="lc-setter-list-card" onSort={this.onSort.bind(this)}>
|
||
{items.map((field, index) => (
|
||
<ArrayItem
|
||
key={field.id}
|
||
scrollIntoView={scrollToLast && index === lastIndex}
|
||
field={field}
|
||
onRemove={this.onRemove.bind(this, field)}
|
||
/>
|
||
))}
|
||
</Sortable>
|
||
</div>
|
||
) : this.props.multiValue ? (
|
||
<Message type="warning">当前选择了多个节点,且值不一致,修改会覆盖所有值</Message>
|
||
) : (
|
||
<Message type="notice">当前项目为空</Message>
|
||
);
|
||
|
||
return (
|
||
<div className="lc-setter-list lc-block-setter">
|
||
{/*<div className="lc-block-setter-actions">
|
||
<Button size="medium" onClick={this.onAdd.bind(this)}>
|
||
<Icon type="add" />
|
||
<span>添加</span>
|
||
</Button>
|
||
</div>*/}
|
||
{content}
|
||
<Button className="lc-setter-list-add" type="primary" onClick={this.onAdd.bind(this)}>
|
||
<Icon type="add" />
|
||
<span>添加一项</span>
|
||
</Button>
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
|
||
class ArrayItem extends Component<{
|
||
field: SettingField;
|
||
onRemove: () => void;
|
||
scrollIntoView: boolean;
|
||
}> {
|
||
shouldComponentUpdate() {
|
||
return false;
|
||
}
|
||
private shell?: HTMLDivElement | null;
|
||
componentDidMount() {
|
||
if (this.props.scrollIntoView && this.shell) {
|
||
this.shell.parentElement!.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||
}
|
||
}
|
||
render() {
|
||
const { onRemove, field } = this.props;
|
||
return (
|
||
<div className="lc-listitem" ref={ref => (this.shell = ref)}>
|
||
<div draggable className="lc-listitem-handler">
|
||
<Icon type="ellipsis" size="small" />
|
||
</div>
|
||
<div className="lc-listitem-body">{createSettingFieldView(field, field.parent)}</div>
|
||
<div className="lc-listitem-actions">
|
||
<div className="lc-listitem-action" onClick={onRemove}>
|
||
<Icon type="ashbin" size="small" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
}
|
||
|
||
class TableSetter extends ListSetter {
|
||
// todo:
|
||
// forceInline = 1
|
||
// has more actions
|
||
}
|
||
|
||
export default class ArraySetter extends Component<{
|
||
value: any[];
|
||
field: SettingField;
|
||
itemConfig?: {
|
||
setter?: SetterType;
|
||
defaultValue?: any | ((field: SettingField) => any);
|
||
required?: boolean;
|
||
};
|
||
mode?: 'popup' | 'list';
|
||
forceInline?: boolean;
|
||
multiValue?: boolean;
|
||
}> {
|
||
static contextType = PopupContext;
|
||
private pipe: any;
|
||
render() {
|
||
const { mode, forceInline, ...props } = this.props;
|
||
const { field, itemConfig } = props;
|
||
if (mode === 'popup' || forceInline) {
|
||
const title = (
|
||
<Fragment>
|
||
编辑:
|
||
<Title title={field.title} />
|
||
</Fragment>
|
||
);
|
||
if (!this.pipe) {
|
||
let width = 360;
|
||
const setter: any = itemConfig?.setter;
|
||
if (setter?.componentName === 'ObjectSetter') {
|
||
const items: FieldConfig[] = setter.props?.config?.items;
|
||
if (items && Array.isArray(items)) {
|
||
const length = items.filter(item => item.required || item.important).length;
|
||
if (length === 3) {
|
||
width = 480;
|
||
} else if (length > 3) {
|
||
width = 600;
|
||
}
|
||
}
|
||
}
|
||
this.pipe = (this.context as PopupPipe).create({ width });
|
||
}
|
||
this.pipe.send(
|
||
<TableSetter key={field.id} {...props} />,
|
||
title,
|
||
);
|
||
return (
|
||
<Button
|
||
onClick={e => {
|
||
this.pipe.show((e as any).target);
|
||
}}
|
||
>
|
||
<Icon type="edit" />
|
||
{forceInline ? title : '编辑数组'}
|
||
</Button>
|
||
);
|
||
} else {
|
||
return <ListSetter {...props} />;
|
||
}
|
||
}
|
||
}
|