2020-03-12 03:47:03 +08:00

280 lines
7.6 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, 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} />;
}
}
}