chore: 移除内置 setters

This commit is contained in:
lihao.ylh 2021-12-21 14:37:13 +08:00
parent 00300cbe8c
commit 32e235fcb1
11 changed files with 0 additions and 1506 deletions

View File

@ -1,314 +0,0 @@
import { Component, Fragment } from 'react';
import { Icon, Button, Message } from '@alifd/next';
import { Title, runInAction } from '@ali/lowcode-editor-core';
import { SetterType, FieldConfig, SetterConfig } from '@ali/lowcode-types';
import { SettingField } from '@ali/lowcode-designer';
import { createSettingFieldView } from '../settings/settings-pane';
import { PopupContext, PopupPipe } from '../popup';
import Sortable from './sortable';
import './style.less';
interface ArraySetterState {
items: SettingField[];
itemsMap: Map<string | number, SettingField>;
}
interface ArraySetterProps {
value: any[];
field: SettingField;
itemSetter?: SetterType;
columns?: FieldConfig[];
multiValue?: boolean;
onChange?: Function;
}
export class ListSetter extends Component<ArraySetterProps, ArraySetterState> {
state: ArraySetterState = {
items: [],
itemsMap: new Map<string | number, SettingField>(),
};
onItemChange = async () => {
const { onChange } = this.props;
const { items } = this.state;
// setValue 的props数据合并会有一些延时这里延时100毫秒来等待数据合并完成获取到最新数据
await this.delay(100);
onChange && onChange(items.map(item => {
return item && item.getValue();
}));
};
componentDidMount() {
const { value, field, onChange } = this.props;
const itemsMap = new Map<string | number, SettingField>();
const items = [];
const valueLength = value && Array.isArray(value) ? value.length : 0;
for (let i = 0; i < valueLength; i++) {
const item = field.createField({
name: i,
setter: this.props.itemSetter,
forceInline: 1,
setValue: this.onItemChange,
});
items[i] = item;
itemsMap.set(item.id, item);
}
this.setState({
items,
itemsMap,
}, () => {
// setValue 会触发onItemChange需要在items被设值之后才能调用
runInAction(() => {
value && value.map((item, index) => {
items[index].setValue(item);
return item;
});
});
});
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
onSort(sortedIds: Array<string | number>) {
const { itemsMap } = this.state;
const { onChange, itemSetter, field } = this.props;
const sortValues = sortedIds.map((id) => {
const value = itemsMap.get(id)!.getValue();
return value;
});
// 对itemsMap重新生成并刷新当前setter数据
const newItems: SettingField[] = [];
// const newItemsMap = {};
itemsMap.clear();
for (let i = 0; i < sortValues.length; i++) {
const newItem = field.createField({
name: i,
setter: itemSetter,
// FIXME:
forceInline: 1,
setValue: this.onItemChange,
});
newItems[i] = newItem;
itemsMap.set(newItem.id, newItem);
}
this.setState({
items: newItems,
itemsMap,
}, () => {
sortValues && sortValues.map((value, index) => {
newItems[index].setValue(value);
return newItems;
});
});
}
private scrollToLast = false;
onAdd() {
const { items, itemsMap } = this.state;
const { itemSetter, onChange } = this.props;
const initialValue = typeof itemSetter === 'object' ? (itemSetter as any).initialValue : null;
const item = this.props.field.createField({
name: items.length,
setter: itemSetter,
// FIXME:
forceInline: 1,
setValue: this.onItemChange,
});
items.push(item);
itemsMap.set(item.id, item);
this.scrollToLast = true;
this.setState({
items: items.slice(),
}, () => {
item.setValue(typeof initialValue === 'function' ? initialValue(item) : initialValue);
});
}
onRemove(field: SettingField) {
const { onChange } = this.props;
const { items, itemsMap } = this.state;
let i = items.indexOf(field);
const values = items.map((item) => {
return item.getValue();
});
if (i < 0) {
return;
}
items.splice(i, 1);
values.splice(i, 1);
const l = items.length;
while (i < l) {
items[i].setKey(i);
i++;
}
itemsMap.delete(field.id);
field.remove();
onChange && onChange(values);
this.setState({ items: items.slice() });
}
componentWillUnmount() {
this.state.items.forEach((field) => {
field.purge();
});
}
render() {
let columns: any = null;
if (this.props.columns) {
columns = this.props.columns.map((column) => (
<Title key={column.name} title={column.title || (column.name as string)} />
));
}
const { items } = this.state;
const { scrollToLast } = this;
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> */}
{columns && <div className="lc-setter-list-columns">{columns}</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;
itemSetter?: SetterType;
mode?: 'popup' | 'list';
forceInline?: boolean;
multiValue?: boolean;
}> {
static contextType = PopupContext;
private pipe: any;
render() {
const { mode, forceInline, ...props } = this.props;
const { field, itemSetter } = props;
let columns: FieldConfig[] | undefined;
if ((itemSetter as SetterConfig)?.componentName === 'ObjectSetter') {
const items: FieldConfig[] = (itemSetter as any).props?.config?.items;
if (items && Array.isArray(items)) {
columns = items.filter((item) => item.isRequired || item.important || (item.setter as any)?.isRequired);
if (columns.length > 4) {
columns = columns.slice(0, 4);
}
}
}
if (mode === 'popup' || forceInline) {
const title = (
<Fragment>
<Title title={field.title} />
</Fragment>
);
if (!this.pipe) {
let width = 360;
if (columns) {
if (columns.length === 3) {
width = 480;
} else if (columns.length > 3) {
width = 600;
}
}
this.pipe = (this.context as PopupPipe).create({ width });
}
this.pipe.send(<TableSetter key={field.id} {...props} columns={columns} />, title);
return (
<Button
type={forceInline ? 'normal' : 'primary'}
onClick={(e) => {
this.pipe.show((e as any).target, field.id);
}}
>
<Icon type="edit" />
{forceInline ? title : '编辑数组'}
</Button>
);
} else {
return <ListSetter {...props} columns={columns?.slice(0, 2)} />;
}
}
}

View File

@ -1,29 +0,0 @@
.lc-sortable {
position: relative;
.lc-sortable-card {
box-sizing: border-box;
&:after, &:before {
content: "";
display: table;
}
&:after {
clear: both;
}
&.lc-dragging {
outline: 2px dashed var(--color-brand);
outline-offset: -2px;
> * {
visibility: hidden;
}
border-color: transparent !important;
box-shadow: none !important;
background: transparent !important;
}
}
[draggable] {
cursor: ns-resize;
}
}

View File

@ -1,223 +0,0 @@
import { Component, Children, ReactElement } from 'react';
import classNames from 'classnames';
import './sortable.less';
class Sortable extends Component<{
className?: string;
itemClassName?: string;
onSort?: (sortedIds: Array<string | number>) => void;
dragImageSourceHandler?: (elem: Element) => Element;
children: ReactElement[];
}> {
private shell?: HTMLDivElement | null;
private items?: Array<string | number>;
private willDetach?: () => void;
componentDidMount() {
const box = this.shell!;
let isDragEnd = false;
/**
* target node to be dragged
*/
let source: Element | null;
/**
* node to be placed
*/
let ref: Element | null;
/**
* next sibling of the source node
*/
let origRef: Element | null;
/**
* accurately locate the node from event
*/
const locate = (e: DragEvent) => {
let y = e.clientY;
if (e.view !== window && e.view!.frameElement) {
y += e.view!.frameElement.getBoundingClientRect().top;
}
let node = box.firstElementChild as HTMLDivElement;
while (node) {
if (node !== source && node.dataset.id) {
const rect = node.getBoundingClientRect();
if (rect.height <= 0) continue;
if (y < rect.top + rect.height / 2) {
break;
}
}
node = node.nextElementSibling as HTMLDivElement;
}
return node;
};
/**
* find the source node
*/
const getSource = (e: DragEvent) => {
const target = e.target as Element;
if (!target || !box.contains(target) || target === box) {
return null;
}
let node = box.firstElementChild;
while (node) {
if (node.contains(target)) {
return node;
}
node = node.nextElementSibling;
}
return null;
};
const sort = (beforeId: string | number | null | undefined) => {
if (!source) return;
const sourceId = (source as HTMLDivElement).dataset.id;
const items = this.items!;
const origIndex = items.findIndex(id => id == sourceId);
let newIndex = beforeId ? items.findIndex(id => id == beforeId) : items.length;
if (origIndex < 0 || newIndex < 0) return;
if (this.props.onSort) {
if (newIndex > origIndex) {
newIndex -= 1;
}
if (origIndex === newIndex) return;
const item = items.splice(origIndex, 1);
items.splice(newIndex, 0, item[0]);
this.props.onSort(items);
}
};
const dragstart = (e: DragEvent) => {
isDragEnd = false;
source = getSource(e);
if (!source) {
return false;
}
origRef = source.nextElementSibling;
const rect = source.getBoundingClientRect();
let dragSource = source;
if (this.props.dragImageSourceHandler) {
dragSource = this.props.dragImageSourceHandler(source);
}
if (e.dataTransfer) {
e.dataTransfer.setDragImage(dragSource, e.clientX - rect.left, e.clientY - rect.top);
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.dropEffect = 'move';
try {
e.dataTransfer.setData('application/json', {} as any);
} catch (ex) {
// eslint-disable-line
}
}
setTimeout(() => {
source!.classList.add('lc-dragging');
}, 0);
return true;
};
const placeAt = (beforeRef: Element | null) => {
if (beforeRef) {
if (beforeRef !== source) {
box.insertBefore(source!, beforeRef);
}
} else {
box.appendChild(source!);
}
};
const adjust = (e: DragEvent) => {
if (isDragEnd) return;
ref = locate(e);
placeAt(ref);
};
let lastDragEvent: DragEvent | null;
const drag = (e: DragEvent) => {
if (!source) return;
e.preventDefault();
if (lastDragEvent) {
if (lastDragEvent.clientX === e.clientX && lastDragEvent.clientY === e.clientY) {
return;
}
}
lastDragEvent = e;
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'move';
}
adjust(e);
};
const dragend = (e: DragEvent) => {
isDragEnd = true;
if (!source) return;
e.preventDefault();
source.classList.remove('lc-dragging');
placeAt(origRef);
sort(ref ? (ref as HTMLDivElement).dataset.id : null);
source = null;
ref = null;
origRef = null;
lastDragEvent = null;
};
box.addEventListener('dragstart', dragstart);
document.addEventListener('dragover', drag);
document.addEventListener('drag', drag);
document.addEventListener('dragend', dragend);
this.willDetach = () => {
box.removeEventListener('dragstart', dragstart);
document.removeEventListener('dragover', drag);
document.removeEventListener('drag', drag);
document.removeEventListener('dragend', dragend);
};
}
componentWillUnmount() {
if (this.willDetach) {
this.willDetach();
}
}
render() {
const { className, itemClassName, children } = this.props;
const items: Array<string | number> = [];
const cards = Children.map(children, child => {
const id = child.key!;
items.push(id);
return (
<div key={id} data-id={id} className={classNames('lc-sortable-card', itemClassName)}>
{child}
</div>
);
});
this.items = items;
return (
<div
className={classNames('lc-sortable', className)}
ref={ref => {
this.shell = ref;
}}
>
{cards}
</div>
);
}
}
export default Sortable;

View File

@ -1,103 +0,0 @@
.lc-setter-list {
[draggable] {
cursor: move;
}
color: var(--color-text);
.next-btn {
display: inline-flex;
align-items: center;
line-height: 1 !important;
max-width: 100%;
text-overflow: ellipsis;
}
.lc-setter-list-add {
display: block;
width: 100%;
margin-top: 8px;
}
.lc-setter-list-columns {
display: flex;
> .lc-title {
flex: 1;
justify-content: center;
}
margin-left: 47px;
margin-right: 28px;
margin-bottom: 5px;
}
.lc-setter-list-scroll-body {
margin: -8px -5px;
padding: 8px 10px;
overflow-y: auto;
max-height: 300px;
}
.lc-setter-list-card {
border: 1px solid rgba(31,56,88,.2);
background-color: var(--color-block-background-light);
border-radius: 3px;
&:not(:last-child) {
margin-bottom: 5px;
}
.lc-listitem {
position: relative;
outline: none;
display: flex;
align-items: stretch;
height: 34px;
.lc-listitem-actions {
margin: 0 3px;
display: inline-flex;
align-items: center;
justify-content: flex-end;
.lc-listitem-action {
text-align: center;
cursor: pointer;
opacity: 0.6;
&:hover {
opacity: 1;
}
}
}
.lc-listitem-body {
flex: 1;
display: flex;
align-items: stretch;
overflow: hidden;
min-width: 0;
text-overflow: ellipsis;
.lc-field {
padding: 0 !important;
display: flex;
align-items: center;
>.lc-field-body {
justify-content: center;
}
}
> * {
width: 100%;
}
.next-btn {
display: block;
width: 100%;
}
}
.lc-listitem-handler {
margin-left: 2px;
display: inline-flex;
align-items: center;
.next-icon-ellipsis {
transform: rotate(90deg);
}
opacity: 0.6;
}
}
}
}

View File

@ -1,368 +0,0 @@
import React, { Component, ComponentClass, ReactNode } from 'react';
import classNames from 'classnames';
import { Dropdown, Menu } from '@alifd/next';
import {
SetterConfig,
CustomView,
DynamicProps,
DynamicSetter,
TitleContent,
isSetterConfig,
isDynamicSetter,
} from '@ali/lowcode-types';
import {
getSetter,
getSettersMap,
computed,
obx,
Title,
createSetterContent,
observer,
shallowIntl,
} from '@ali/lowcode-editor-core';
import { IconConvert } from '../../icons/convert';
import { intlNode } from '../../locale';
import './style.less';
import { SettingField } from '@ali/lowcode-designer';
import { IconVariable } from '../../icons/variable';
export interface SetterItem {
name: string;
title: TitleContent;
setter: string | DynamicSetter | CustomView;
props?: object | DynamicProps;
condition?: (field: SettingField) => boolean;
initialValue?: any | ((field: SettingField) => any);
list: boolean;
}
function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | DynamicSetter>): SetterItem[] {
if (!setters) {
const normalized: SetterItem[] = [];
getSettersMap().forEach((setter, name) => {
if (name === 'MixedSetter') {
return;
}
normalized.push({
name,
title: setter.title || name,
setter: name,
condition: setter.condition,
initialValue: setter.initialValue,
list: setter.recommend || false,
});
});
return normalized;
}
const names: string[] = [];
function generateName(n: string) {
let idx = 1;
let got = n;
while (names.indexOf(got) > -1) {
got = `${n}:${idx++}`;
}
names.push(got);
return got;
}
return setters.map((setter) => {
const config: any = {
setter,
list: true,
};
if (isSetterConfig(setter)) {
config.setter = setter.componentName;
config.props = setter.props;
config.condition = setter.condition;
config.initialValue = setter.initialValue;
config.title = setter.title;
}
if (typeof config.setter === 'string') {
config.name = config.setter;
names.push(config.name);
const info = getSetter(config.setter);
if (!config.title) {
config.title = info?.title || config.setter;
}
if (!config.condition) {
config.condition = info?.condition;
}
if (!config.initialValue) {
config.initialValue = info?.initialValue;
}
} else {
config.name = generateName((config.setter as any)?.displayName || (config.setter as any)?.name || 'CustomSetter');
if (!config.title) {
config.title = config.name;
}
}
return config;
});
}
interface VariableSetter extends ComponentClass {
show(params: object): void;
}
@observer
export default class MixedSetter extends Component<{
field: SettingField;
setters?: Array<string | SetterConfig | CustomView | DynamicSetter>;
onSetterChange?: (field: SettingField, name: string) => void;
onChange?: (val: any) => void;
value?: any;
className?: string;
}> {
private setters = nomalizeSetters(this.props.setters);
// set name ,used in setting Transducer
static displayName = 'MixedSetter';
@obx.ref private used?: string;
@computed private getCurrentSetter() {
const { field } = this.props;
let firstMatched: SetterItem | undefined;
let firstDefault: SetterItem | undefined;
for (const setter of this.setters) {
if (setter.name === this.used) {
return setter;
}
if (!setter.condition) {
if (!firstDefault) {
firstDefault = setter;
}
continue;
}
if (!firstMatched && setter.condition(field)) {
firstMatched = setter;
}
}
return firstMatched || firstDefault || this.setters[0];
}
// dirty fix vision variable setter logic
private hasVariableSetter = this.setters.some((item) => item.name === 'VariableSetter');
private useSetter = (name: string) => {
const { field } = this.props;
if (name === 'VariableSetter') {
const setterComponent = getSetter('VariableSetter')?.component as any;
if (setterComponent && setterComponent.isPopup) {
setterComponent.show({ prop: field });
return;
}
}
if (name === this.used) {
return;
}
const setter = this.setters.find((item) => item.name === name);
// reset hotValue
field.setHotValue(undefined);
this.used = name;
if (setter) {
this.handleInitial(setter);
}
};
private handleInitial({ initialValue }: SetterItem) {
const { field, onChange } = this.props;
let newValue: any = initialValue;
if (newValue && typeof newValue === 'function') {
newValue = newValue(field);
}
onChange && onChange(newValue);
}
private shell: HTMLDivElement | null = null;
private checkIsBlockField() {
if (this.shell) {
const setter = this.shell.firstElementChild;
if (setter && setter.classList.contains('lc-block-setter')) {
this.shell.classList.add('lc-block-setter');
} else {
this.shell.classList.remove('lc-block-setter');
}
}
}
componentDidUpdate() {
this.checkIsBlockField();
}
componentDidMount() {
this.checkIsBlockField();
}
private renderCurrentSetter(currentSetter?: SetterItem, extraProps?: object) {
const { className, field, setters, onSetterChange, ...restProps } = this.props;
if (!currentSetter) {
// TODO: use intl
if (restProps.value == null) {
return <span>NullValue</span>;
} else {
return <span>InvalidValue</span>;
}
}
const { setter, props } = currentSetter;
let setterProps: any = {};
let setterType: any;
let dynamicProps: any = {};
if (isDynamicSetter(setter)) {
setterType = setter.call(field, field);
// { componentName: string; props: object }
if (typeof setterType === 'object' && typeof setterType.componentName === 'string') {
dynamicProps = setterType.props || {};
setterType = setterType.componentName;
}
} else {
setterType = setter;
}
if (props) {
setterProps = props;
if (typeof setterProps === 'function') {
setterProps = setterProps(field);
}
}
return createSetterContent(setterType, {
...shallowIntl(setterProps),
field,
...restProps,
...extraProps,
...dynamicProps,
onInitial: () => {
this.handleInitial(currentSetter);
},
});
}
private contentsFromPolyfill(setterComponent: VariableSetter) {
const { field } = this.props;
const n = this.setters.length;
let setterContent: any;
let actions: any;
if (n < 3) {
const tipContent = field.isUseVariable()
? intlNode('Binded: {expr}', { expr: field.getVariableValue() })
: intlNode('Variable Binding');
if (n === 1) {
// =1: 原地展示<当前绑定的值,点击调用 VariableSetter.show>icon 高亮是否->isUseVaiable点击 VariableSetter.show
setterContent = (
<a
onClick={() => {
setterComponent.show({ prop: field });
}}
>
{tipContent}
</a>
);
} else {
// =2: 另外一个 Setter 原地展示icon 高亮,点击弹出调用 VariableSetter.show
// FIXME! use variable placeholder setter
const otherSetter = this.setters.find((item) => item.name !== 'VariableSetter')!;
setterContent = this.renderCurrentSetter(otherSetter, {
value: field.getMockOrValue(),
});
}
actions = (
<Title
className={field.isUseVariable() ? 'variable-binded' : ''}
title={{
icon: <IconVariable size={24} />,
tip: tipContent,
}}
onClick={() => {
setterComponent.show({ prop: field });
}}
/>
);
} else {
// >=3: 原地展示当前 setter<当前绑定的值,点击调用 VariableSetter.show>icon tip 提示绑定的值,点击展示切换 Setter点击其它 setter 直接切换,点击 Variable Setter-> VariableSetter.show
const currentSetter = field.isUseVariable()
? this.setters.find((item) => item.name === 'VariableSetter')
: this.getCurrentSetter();
if (currentSetter?.name === 'VariableSetter') {
setterContent = (
<a
onClick={() => {
setterComponent.show({ prop: field });
}}
>
{intlNode('Binded: {expr}', { expr: field.getVariableValue() })}
</a>
);
} else {
setterContent = this.renderCurrentSetter(currentSetter);
}
actions = this.renderSwitchAction(currentSetter);
}
return {
setterContent,
actions,
};
}
private renderSwitchAction(currentSetter?: SetterItem) {
const usedName = currentSetter?.name || this.used;
const triggerNode = (
<Title
title={{
tip: intlNode('Switch Setter'),
// FIXME: got a beautiful icon
icon: <IconConvert size={24} />,
}}
className="lc-switch-trigger"
/>
);
return (
<Dropdown trigger={triggerNode} triggerType="click" align="tr br">
<Menu selectMode="single" hasSelectedIcon selectedKeys={usedName} onItemClick={this.useSetter}>
{this.setters
.filter((setter) => setter.list || setter.name === usedName)
.map((setter) => {
return (
<Menu.Item key={setter.name}>
<Title title={setter.title} />
</Menu.Item>
);
})}
</Menu>
</Dropdown>
);
}
render() {
const { className } = this.props;
let contents: {
setterContent: ReactNode,
actions: ReactNode,
} | undefined;
if (this.hasVariableSetter) {
// polyfill vision variable setter logic
const setterComponent = getSetter('VariableSetter')?.component as any;
if (setterComponent && setterComponent.isPopup) {
contents = this.contentsFromPolyfill(setterComponent);
}
}
if (!contents) {
const currentSetter = this.getCurrentSetter();
contents = {
setterContent: this.renderCurrentSetter(currentSetter),
actions: this.renderSwitchAction(currentSetter),
};
}
return (
<div ref={(shell) => { this.shell = shell; }} className={classNames('lc-setter-mixed', className)}>
{contents.setterContent}
<div className="lc-setter-actions">{contents.actions}</div>
</div>
);
}
}

View File

@ -1,70 +0,0 @@
.lc-setter-mixed {
min-width: 0;
margin-right: 26px;
display: block;
position: relative;
>.lc-setter-actions {
position: absolute;
right: -4px;
top: 50%;
transform: translate(100%, -50%);
.lc-switch-trigger {
cursor: pointer;
opacity: 0.6;
&:hover {
opacity: 1;
}
}
.lc-title {
cursor: pointer;
.lc-title-icon svg {
fill: #8f9bb3;
}
}
.variable-binded {
background: var(--color-brand, #006cff);
color: #fff!important;
border-radius: 3px;
.lc-title-icon svg {
fill: #fff;
}
}
}
.next-input,.next-date-picker,.next-month-picker {
width: 100%;
}
&.lc-block-setter {
position: static;
margin-right: 0;
>.lc-setter-actions {
transform: none;
}
}
}
.lc-block-field > .lc-field-body > .lc-setter-mixed {
position: static;
margin-right: 0;
>.lc-setter-actions {
position: absolute;
right: 12px;
top: 0;
height: 32px;
transform: none;
}
}
.lc-block-field > .lc-field-body > .lc-setter-mixed{
}
.lc-accordion-field > .lc-field-body > .lc-setter-mixed {
position: static;
margin-right: 0;
> .lc-setter-actions {
right: 36px;
top: 4px;
transform: none;
}
}

View File

@ -1,203 +0,0 @@
import { Component, Fragment } from 'react';
import { Icon, Button } from '@alifd/next';
import { SetterType, FieldConfig, CustomView } from '@ali/lowcode-types';
import { createSettingFieldView } from '../settings/settings-pane';
import { PopupContext, PopupPipe } from '../popup';
import { isSettingField, SettingField } from '@ali/lowcode-designer';
import './style.less';
import { Title } from '@ali/lowcode-editor-core';
export default class ObjectSetter extends Component<{
field: SettingField;
descriptor?: string | ((rowField: SettingField) => string);
config: ObjectSetterConfig;
mode?: 'popup' | 'form';
// 1: in tablerow 2: in listrow 3: in column-cell
forceInline?: number;
}> {
render() {
const { mode, forceInline = 0, ...props } = this.props;
if (forceInline || mode === 'popup') {
if (forceInline > 2 || mode === 'popup') {
// popup
return <RowSetter {...props} primaryButton={!forceInline} />;
} else {
return <RowSetter columns={forceInline > 1 ? 2 : 4} {...props} />;
}
} else {
// form
return <FormSetter {...props} />;
}
}
}
interface ObjectSetterConfig {
items?: FieldConfig[];
extraSetter?: SetterType;
}
interface RowSetterProps {
field: SettingField;
descriptor?: string | ((rowField: SettingField) => string);
config: ObjectSetterConfig;
columns?: number;
primaryButton?: boolean;
}
class RowSetter extends Component<RowSetterProps> {
static contextType = PopupContext;
state: any = {
descriptor: '',
};
private items?: SettingField[];
constructor(props: RowSetterProps) {
super(props);
const { config, descriptor, field, columns } = props;
const items: SettingField[] = [];
if (columns && config?.items) {
const l = Math.min(config.items.length, columns);
for (let i = 0; i < l; i++) {
const conf = config.items[i];
if (conf.isRequired || conf.important || (conf.setter as any)?.isRequired) {
const item = field.createField({
...conf,
// in column-cell
forceInline: 3,
});
items.push(item);
}
}
}
if (items.length > 0) {
this.items = items;
}
let firstRun = true;
field.onEffect(() => {
const state: any = {};
if (descriptor) {
if (typeof descriptor === 'function') {
state.descriptor = descriptor(field);
} else {
state.descriptor = field.getPropValue(descriptor);
}
} else {
state.descriptor = field.title;
}
if (firstRun) {
firstRun = false;
this.state = state;
} else {
this.setState(state);
}
});
}
shouldComponentUpdate(_: any, nextState: any) {
if (this.state.decriptor !== nextState.decriptor) {
return true;
}
return false;
}
private pipe: any;
render() {
const { items } = this;
const { field, primaryButton, config } = this.props;
if (!this.pipe) {
this.pipe = (this.context as PopupPipe).create({ width: 320 });
}
const title = (
<Fragment>
<Title title={this.state.descriptor} />
</Fragment>
);
this.pipe.send(<FormSetter key={field.id} field={field} config={config} />, title);
if (items) {
return (
<div className="lc-setter-object-row">
<div
className="lc-setter-object-row-edit"
onClick={(e) => {
this.pipe.show((e as any).target, field.id);
}}
>
<Icon size="small" type="edit" />
</div>
<div className="lc-setter-object-row-body">{items.map((item) => createSettingFieldView(item, field))}</div>
</div>
);
}
return (
<Button
type={primaryButton === false ? 'normal' : 'primary'}
onClick={(e) => {
this.pipe.show((e as any).target, field.id);
}}
>
<Icon type="edit" />
{title}
</Button>
);
}
}
interface FormSetterProps {
field: SettingField;
config: ObjectSetterConfig;
}
class FormSetter extends Component<FormSetterProps> {
private items: (SettingField | CustomView)[];
constructor(props: RowSetterProps) {
super(props);
const { config, field } = props;
const { extraProps } = field;
if (Array.isArray(field.items) && field.items.length > 0) {
field.items.forEach((item: SettingField | CustomView) => {
if (isSettingField(item)) {
const originalSetValue = item.extraProps.setValue;
item.extraProps.setValue = (...args) => {
// 调用子字段本身的 setValue
originalSetValue?.apply(null, args);
// 调用父字段本身的 setValue
extraProps.setValue?.apply(null, args);
};
}
});
this.items = field.items;
} else {
this.items = (config?.items || []).map((conf) => field.createField({
...conf,
setValue: extraProps?.setValue,
}));
}
// TODO: extraConfig for custom fields
}
shouldComponentUpdate() {
return false;
}
render() {
const { field } = this.props;
return (
<div className="lc-setter-object lc-block-setter">
{this.items.map((item, index) => createSettingFieldView(item, field, index))}
</div>
);
}
}

View File

@ -1,31 +0,0 @@
.lc-setter-object-row {
display: flex;
align-items: stretch;
width: 100%;
.lc-setter-object-row-edit {
width: 20px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.lc-setter-object-row-body {
display: flex;
flex: 1;
min-width: 0;
align-items: center;
.lc-field {
padding: 0 !important;
.lc-field-body {
padding: 0 !important; margin: 0 !important;
}
}
> * {
flex: 1;
flex-shrink: 1;
margin-left: 2px;
min-width: 0;
overflow: hidden;
}
}
}

View File

@ -1,12 +0,0 @@
.lc-setter-slot {
display: flex;
align-items: start;
.lc-slot-params {
margin-top: 5px;
}
}
.lc-setter-slot-column {
display: flex;
flex-direction: column;
}

View File

@ -1,100 +0,0 @@
import { Component } from 'react';
import { isJSSlot } from '@ali/lowcode-types';
import { Button, Input, Icon } from '@alifd/next';
import './index.less';
export default class SlotSetter extends Component<{
value: any;
onChange?: (value: any) => void;
onInitial?: () => void;
// 是否支持设置入参
supportParams?: boolean;
}> {
private handleInitial = () => {
const { onChange, onInitial } = this.props;
if (onInitial) {
onInitial();
return;
}
if (!onChange) {
return;
}
onChange({
type: 'JSSlot',
value: null,
});
};
render() {
const { value, onChange, supportParams } = this.props;
if (!isJSSlot(value)) {
return (
<Button type="primary" onClick={this.handleInitial}>
</Button>
);
}
const hasParams = value.params && Array.isArray(value.params);
return (
<div className="lc-setter-slot lc-setter-slot-column">
<Button
onClick={() => {
// TODO: use slot first child literal value pad
onChange && onChange(null);
}}
type="secondary"
>
</Button>
{hasParams ? (
<Input
className="lc-slot-params"
addonTextBefore="入参"
placeholder="插槽入参,以逗号风格"
value={value.params!.join(',')}
autoFocus
onChange={(val) => {
val = val.trim();
const params = val ? val.split(/ *, */) : [];
onChange &&
onChange({
...value,
params: params.length == 0 ? [''] : params,
});
}}
addonAfter={
<Button
type="secondary"
onClick={() => {
onChange &&
onChange({
...value,
params: [''],
});
}}
>
<Icon type="close" />
</Button>
}
/>
) : supportParams ? (
<Button
className="lc-slot-params"
type="primary"
onClick={() => {
onChange &&
onChange({
...value,
params: [],
});
}}
>
</Button>
) : null}
</div>
);
}
}

View File

@ -1,62 +1,9 @@
import { registerSetter } from '@ali/lowcode-editor-core';
import { registerMetadataTransducer } from '@ali/lowcode-designer';
import ArraySetter from './components/array-setter';
import ObjectSetter from './components/object-setter';
import MixedSetter from './components/mixed-setter';
import { isPlainObject } from '@ali/lowcode-utils';
import parseJSFunc from './transducers/parse-func';
import parseProps from './transducers/parse-props';
import addonCombine from './transducers/addon-combine';
import SlotSetter from './components/slot-setter';
import { isJSSlot } from '@ali/lowcode-types';
export const registerDefaults = () => {
registerSetter('ArraySetter', {
component: ArraySetter,
defaultProps: {},
title: 'ArraySetter', // TODO
condition: (field: any) => {
const v = field.getValue();
return v == null || Array.isArray(v);
},
initialValue: [],
recommend: true,
});
registerSetter('ObjectSetter', {
component: ObjectSetter,
// todo: defaultProps
defaultProps: {},
title: 'ObjectSetter', // TODO
condition: (field: any) => {
const v = field.getValue();
return v == null || isPlainObject(v);
},
initialValue: {},
recommend: true,
});
registerSetter('SlotSetter', {
component: SlotSetter,
title: {
type: 'i18n',
'zh-CN': '插槽输入',
'en-US': 'Slot Setter',
},
condition: (field: any) => {
return isJSSlot(field.getValue());
},
initialValue: (field: any, value: any) => {
if (isJSSlot(value)) {
return value;
}
return {
type: 'JSSlot',
value,
};
},
recommend: true,
});
registerSetter('MixedSetter', MixedSetter);
// parseFunc
registerMetadataTransducer(parseJSFunc, 1, 'parse-func');