mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-15 05:36:39 +00:00
complete array-setter
This commit is contained in:
parent
72a7d83f02
commit
b59934eb2f
@ -6,7 +6,7 @@
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.lc-settings-pane {
|
||||
.lc-settings-main {
|
||||
width: 300px;
|
||||
border-left: 1px solid rgba(31,56,88,.1);
|
||||
}
|
||||
|
||||
@ -153,7 +153,15 @@ export class ComponentType {
|
||||
}, {
|
||||
name: 'name',
|
||||
title: '名称',
|
||||
setter: 'StringSetter'
|
||||
setter: {
|
||||
componentName: 'ArraySetter',
|
||||
props: {
|
||||
itemConfig: {
|
||||
setter: 'StringSetter',
|
||||
defaultValue: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
name: 'size',
|
||||
title: '大小',
|
||||
|
||||
@ -272,7 +272,7 @@ export default class Node {
|
||||
* 设置单个属性值
|
||||
*/
|
||||
setPropValue(path: string, value: any) {
|
||||
this.getProp(path, true)!.value = value;
|
||||
this.getProp(path, true)!.setValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -5,7 +5,7 @@ import Props from './props';
|
||||
export type PendingItem = Prop[];
|
||||
export default class PropStash implements IPropParent {
|
||||
@obx.val private space: Set<Prop> = new Set();
|
||||
@computed private get maps(): Map<string, Prop> {
|
||||
@computed private get maps(): Map<string | number, Prop> {
|
||||
const maps = new Map();
|
||||
if (this.space.size > 0) {
|
||||
this.space.forEach(prop => {
|
||||
@ -38,7 +38,7 @@ export default class PropStash implements IPropParent {
|
||||
});
|
||||
}
|
||||
|
||||
get(key: string): Prop {
|
||||
get(key: string | number): Prop {
|
||||
let prop = this.maps.get(key);
|
||||
if (!prop) {
|
||||
prop = new Prop(this, UNSET, key);
|
||||
|
||||
@ -34,15 +34,15 @@ export default class Prop implements IPropParent {
|
||||
/**
|
||||
* 属性值
|
||||
*/
|
||||
@computed get value(): CompositeValue {
|
||||
@computed get value(): CompositeValue | UNSET {
|
||||
return this.export(true);
|
||||
}
|
||||
|
||||
export(serialize = false): CompositeValue {
|
||||
export(serialize = false): CompositeValue | UNSET {
|
||||
const type = this._type;
|
||||
|
||||
if (type === 'unset') {
|
||||
return null;
|
||||
return UNSET;
|
||||
}
|
||||
|
||||
if (type === 'literal' || type === 'expression') {
|
||||
@ -62,7 +62,10 @@ export default class Prop implements IPropParent {
|
||||
}
|
||||
const maps: any = {};
|
||||
this.items!.forEach((prop, key) => {
|
||||
maps[key] = prop.value;
|
||||
const v = prop.export(serialize);
|
||||
if (v !== UNSET) {
|
||||
maps[key] = v;
|
||||
}
|
||||
});
|
||||
return maps;
|
||||
}
|
||||
@ -71,7 +74,10 @@ export default class Prop implements IPropParent {
|
||||
if (!this._items) {
|
||||
return this._items;
|
||||
}
|
||||
return this.items!.map(prop => prop.value);
|
||||
return this.items!.map(prop => {
|
||||
const v = prop.export(serialize);
|
||||
return v === UNSET ? null : v
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -103,7 +109,7 @@ export default class Prop implements IPropParent {
|
||||
/**
|
||||
* set value, val should be JSON Object
|
||||
*/
|
||||
set value(val: CompositeValue) {
|
||||
setValue(val: CompositeValue) {
|
||||
this._value = val;
|
||||
const t = typeof val;
|
||||
if (val == null) {
|
||||
@ -183,44 +189,24 @@ export default class Prop implements IPropParent {
|
||||
return this._type === 'unset';
|
||||
}
|
||||
|
||||
isEqual(otherProp: Prop | null): boolean {
|
||||
if (!otherProp) {
|
||||
return this.isUnset();
|
||||
// TODO: improve this logic
|
||||
compare(other: Prop | null): number {
|
||||
if (!other || other.isUnset()) {
|
||||
return this.isUnset() ? 0 : 2;
|
||||
}
|
||||
if (otherProp.type !== this.type) {
|
||||
return false;
|
||||
if (other.type !== this.type) {
|
||||
return 2;
|
||||
}
|
||||
// 'unset' | 'literal' | 'map' | 'list' | 'expression' | 'slot'
|
||||
return this.code === otherProp.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 值是否是带类型的 JS
|
||||
* 比如 JSExpresion | JSSlot 等值
|
||||
*/
|
||||
@computed isTypedJS(): boolean {
|
||||
const type = this._type;
|
||||
if (type === 'expression' || type === 'slot') {
|
||||
return true;
|
||||
// list
|
||||
if (this.type === 'list') {
|
||||
return this.size === other.size ? 1 : 2;
|
||||
}
|
||||
if (type === 'literal' || type === 'unset') {
|
||||
return false;
|
||||
}
|
||||
if ((type === 'list' || type === 'map') && this.items) {
|
||||
return this.items.some(item => item.isTypedJS());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否简单 JSON 数据
|
||||
*/
|
||||
@computed isJSON() {
|
||||
return !this.isTypedJS();
|
||||
// 'literal' | 'map' | 'expression' | 'slot'
|
||||
return this.code === other.code ? 0 : 2;
|
||||
}
|
||||
|
||||
@obx.val private _items: Prop[] | null = null;
|
||||
@obx.val private _maps: Map<string, Prop> | null = null;
|
||||
@obx.val private _maps: Map<string | number, Prop> | null = null;
|
||||
@computed private get items(): Prop[] | null {
|
||||
let _items: any;
|
||||
untracked(() => {
|
||||
@ -255,8 +241,8 @@ export default class Prop implements IPropParent {
|
||||
}
|
||||
return _items;
|
||||
}
|
||||
@computed private get maps(): Map<string, Prop> | null {
|
||||
if (!this.items || this.items.length < 1) {
|
||||
@computed private get maps(): Map<string | number, Prop> | null {
|
||||
if (!this.items) {
|
||||
return null;
|
||||
}
|
||||
return this._maps;
|
||||
@ -283,7 +269,7 @@ export default class Prop implements IPropParent {
|
||||
) {
|
||||
this.props = parent.props;
|
||||
if (value !== UNSET) {
|
||||
this.value = value;
|
||||
this.setValue(value);
|
||||
}
|
||||
this.key = key;
|
||||
this.spread = spread;
|
||||
@ -291,43 +277,50 @@ export default class Prop implements IPropParent {
|
||||
|
||||
/**
|
||||
* 获取某个属性
|
||||
* @param stash 强制
|
||||
* @param stash 如果不存在,临时获取一个待写入
|
||||
*/
|
||||
get(path: string, stash: false): Prop | null;
|
||||
/**
|
||||
* 获取某个属性, 如果不存在,临时获取一个待写入
|
||||
* @param stash 强制
|
||||
*/
|
||||
get(path: string, stash: true): Prop;
|
||||
/**
|
||||
* 获取某个属性, 如果不存在,临时获取一个待写入
|
||||
*/
|
||||
get(path: string): Prop;
|
||||
get(path: string, stash = true) {
|
||||
get(path: string | number, stash = true): Prop | null {
|
||||
const type = this._type;
|
||||
// todo: support list get
|
||||
if (type !== 'map' && type !== 'unset' && !stash) {
|
||||
if (type !== 'map' && type !== 'list' && type !== 'unset' && !stash) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const maps = type === 'map' ? this.maps : null;
|
||||
|
||||
let prop: any = maps ? maps.get(path) : null;
|
||||
const items = type === 'list' ? this.items : null;
|
||||
let prop: any;
|
||||
if (type === 'list') {
|
||||
if (isValidArrayIndex(path, this.size)) {
|
||||
prop = items![path];
|
||||
}
|
||||
} else if (type === 'map') {
|
||||
prop = maps?.get(path);
|
||||
}
|
||||
|
||||
if (prop) {
|
||||
return prop;
|
||||
}
|
||||
|
||||
const i = path.indexOf('.');
|
||||
let entry = path;
|
||||
let nest = '';
|
||||
if (i > 0) {
|
||||
nest = path.slice(i + 1);
|
||||
if (nest) {
|
||||
entry = path.slice(0, i);
|
||||
prop = maps ? maps.get(entry) : null;
|
||||
if (prop) {
|
||||
return prop.get(nest, stash);
|
||||
if (typeof path !== 'number') {
|
||||
const i = path.indexOf('.');
|
||||
if (i > 0) {
|
||||
nest = path.slice(i + 1);
|
||||
if (nest) {
|
||||
entry = path.slice(0, i);
|
||||
|
||||
if (type === 'list') {
|
||||
if (isValidArrayIndex(entry, this.size)) {
|
||||
prop = items![entry];
|
||||
}
|
||||
} else if (type === 'map') {
|
||||
prop = maps?.get(entry);
|
||||
}
|
||||
|
||||
if (prop) {
|
||||
return prop.get(nest, stash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -336,7 +329,9 @@ export default class Prop implements IPropParent {
|
||||
if (!this.stash) {
|
||||
this.stash = new PropStash(this.props, item => {
|
||||
// item take effect
|
||||
this.set(String(item.key), item);
|
||||
if (item.key) {
|
||||
this.set(item.key, item, true);
|
||||
}
|
||||
item.parent = this;
|
||||
});
|
||||
}
|
||||
@ -389,7 +384,7 @@ export default class Prop implements IPropParent {
|
||||
/**
|
||||
* 元素个数
|
||||
*/
|
||||
size(): number {
|
||||
get size(): number {
|
||||
return this.items?.length || 0;
|
||||
}
|
||||
|
||||
@ -404,7 +399,7 @@ export default class Prop implements IPropParent {
|
||||
return null;
|
||||
}
|
||||
if (type === 'unset' || (force && type !== 'list')) {
|
||||
this.value = [];
|
||||
this.setValue([]);
|
||||
}
|
||||
const prop = new Prop(this, value);
|
||||
this.items!.push(prop);
|
||||
@ -416,29 +411,44 @@ export default class Prop implements IPropParent {
|
||||
*
|
||||
* @param force 强制
|
||||
*/
|
||||
set(key: string, value: CompositeValue | Prop, force = false) {
|
||||
set(key: string | number, value: CompositeValue | Prop, force = false) {
|
||||
const type = this._type;
|
||||
if (type !== 'map' && type !== 'unset' && !force) {
|
||||
if (type !== 'map' && type !== 'list' && type !== 'unset' && !force) {
|
||||
return null;
|
||||
}
|
||||
if (type === 'unset' || (force && type !== 'map')) {
|
||||
this.value = {};
|
||||
if (isValidArrayIndex(key)) {
|
||||
if (type !== 'list') {
|
||||
this.setValue([]);
|
||||
}
|
||||
} else {
|
||||
this.setValue({});
|
||||
}
|
||||
}
|
||||
const prop = isProp(value) ? value : new Prop(this, value, key);
|
||||
const items = this.items!;
|
||||
const maps = this.maps!;
|
||||
const orig = maps.get(key);
|
||||
if (orig) {
|
||||
// replace
|
||||
const i = items.indexOf(orig);
|
||||
if (i > -1) {
|
||||
items.splice(i, 1, prop)[0].purge();
|
||||
if (this.type === 'list') {
|
||||
if (!isValidArrayIndex(key)) {
|
||||
return null;
|
||||
}
|
||||
items[key] = prop;
|
||||
} else if (this.maps) {
|
||||
const maps = this.maps;
|
||||
const orig = maps.get(key);
|
||||
if (orig) {
|
||||
// replace
|
||||
const i = items.indexOf(orig);
|
||||
if (i > -1) {
|
||||
items.splice(i, 1, prop)[0].purge();
|
||||
}
|
||||
maps.set(key, prop);
|
||||
} else {
|
||||
// push
|
||||
items.push(prop);
|
||||
maps.set(key, prop);
|
||||
}
|
||||
maps.set(key, prop);
|
||||
} else {
|
||||
// push
|
||||
items.push(prop);
|
||||
maps.set(key, prop);
|
||||
return null;
|
||||
}
|
||||
|
||||
return prop;
|
||||
@ -533,3 +543,8 @@ export default class Prop implements IPropParent {
|
||||
export function isProp(obj: any): obj is Prop {
|
||||
return obj && obj.isProp;
|
||||
}
|
||||
|
||||
function isValidArrayIndex(key: any, limit: number = -1): key is number {
|
||||
const n = parseFloat(String(key));
|
||||
return n >= 0 && Math.floor(n) === n && isFinite(n) && (limit < 0 || n < limit);
|
||||
}
|
||||
|
||||
@ -2,12 +2,9 @@ import { computed, obx } from '@recore/obx';
|
||||
import { uniqueId } from '../../../../../../utils/unique-id';
|
||||
import { CompositeValue, PropsList, PropsMap } from '../../../schema';
|
||||
import PropStash from './prop-stash';
|
||||
import Prop, { IPropParent } from './prop';
|
||||
import Prop, { IPropParent, UNSET } from './prop';
|
||||
import { NodeParent } from '../node';
|
||||
|
||||
export const UNSET = Symbol.for('unset');
|
||||
export type UNSET = typeof UNSET;
|
||||
|
||||
export default class Props implements IPropParent {
|
||||
readonly id = uniqueId('props');
|
||||
@obx.val private items: Prop[] = [];
|
||||
@ -56,6 +53,7 @@ export default class Props implements IPropParent {
|
||||
|
||||
import(value?: PropsMap | PropsList | null) {
|
||||
this.stash.clear();
|
||||
const originItems = this.items;
|
||||
if (Array.isArray(value)) {
|
||||
this.type = 'list';
|
||||
this.items = value.map(item => new Prop(this, item.value, item.name, item.spread));
|
||||
@ -66,12 +64,12 @@ export default class Props implements IPropParent {
|
||||
this.type = 'map';
|
||||
this.items = [];
|
||||
}
|
||||
this.items.forEach(item => item.purge());
|
||||
originItems.forEach(item => item.purge());
|
||||
}
|
||||
|
||||
merge(value: PropsMap) {
|
||||
Object.keys(value).forEach(key => {
|
||||
this.query(key).value = value[key];
|
||||
this.query(key, true)!.setValue(value[key]);
|
||||
});
|
||||
}
|
||||
|
||||
@ -80,11 +78,14 @@ export default class Props implements IPropParent {
|
||||
return null;
|
||||
}
|
||||
if (this.type === 'list') {
|
||||
return this.items.map(item => ({
|
||||
spread: item.spread,
|
||||
name: item.key as string,
|
||||
value: item.export(serialize),
|
||||
}));
|
||||
return this.items.map(item => {
|
||||
const v = item.export(serialize);
|
||||
return {
|
||||
spread: item.spread,
|
||||
name: item.key as string,
|
||||
value: v === UNSET ? null : v,
|
||||
};
|
||||
});
|
||||
}
|
||||
const maps: any = {};
|
||||
this.items.forEach(prop => {
|
||||
@ -95,26 +96,12 @@ export default class Props implements IPropParent {
|
||||
return maps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 path 路径查询属性,如果没有则临时生成一个
|
||||
*/
|
||||
query(path: string): Prop;
|
||||
/**
|
||||
* 根据 path 路径查询属性
|
||||
*
|
||||
* @useStash 如果没有则临时生成一个
|
||||
*/
|
||||
query(path: string, useStash: true): Prop;
|
||||
/**
|
||||
* 根据 path 路径查询属性
|
||||
*/
|
||||
query(path: string, useStash: false): Prop | null;
|
||||
/**
|
||||
* 根据 path 路径查询属性
|
||||
*
|
||||
* @useStash 如果没有则临时生成一个
|
||||
*/
|
||||
query(path: string, useStash: boolean = true) {
|
||||
query(path: string, useStash: boolean = true): Prop | null {
|
||||
let matchedLength = 0;
|
||||
let firstMatched = null;
|
||||
if (this.items) {
|
||||
@ -156,17 +143,7 @@ export default class Props implements IPropParent {
|
||||
* 获取某个属性, 如果不存在,临时获取一个待写入
|
||||
* @param useStash 强制
|
||||
*/
|
||||
get(path: string, useStash: true): Prop;
|
||||
/**
|
||||
* 获取某个属性
|
||||
* @param useStash 强制
|
||||
*/
|
||||
get(path: string, useStash: false): Prop | null;
|
||||
/**
|
||||
* 获取某个属性
|
||||
*/
|
||||
get(path: string): Prop | null;
|
||||
get(name: string, useStash = false) {
|
||||
get(name: string, useStash = false): Prop | null {
|
||||
return this.maps.get(name) || (useStash && this.stash.get(name)) || null;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,233 @@
|
||||
import { Component } from 'react';
|
||||
import { Icon, Button, Message } from '@alifd/next';
|
||||
import Sortable from './sortable';
|
||||
import { SettingField, SetterType } from '../../main';
|
||||
import './style.less';
|
||||
import { createSettingFieldView } from '../../settings-pane';
|
||||
|
||||
interface ArraySetterState {
|
||||
items: SettingField[];
|
||||
itemsMap: Map<string | number, SettingField>;
|
||||
prevLength: number;
|
||||
}
|
||||
export class ListSetter extends Component<
|
||||
{
|
||||
value: any[];
|
||||
field: SettingField;
|
||||
itemConfig?: {
|
||||
setter?: SetterType;
|
||||
defaultValue?: any | ((field: SettingField, editor: any) => any);
|
||||
required?: boolean;
|
||||
};
|
||||
multiValue?: boolean;
|
||||
},
|
||||
ArraySetterState
|
||||
> {
|
||||
static getDerivedStateFromProps(props: any, 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: 1,
|
||||
});
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
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, item.editor) : 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;
|
||||
|
||||
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>
|
||||
{this.props.multiValue && <Message type="warning">当前选择多个节点,且值不一致</Message>}
|
||||
{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>
|
||||
) : (
|
||||
<div className="lc-setter-list-empty">
|
||||
列表为空
|
||||
<Button size="small" onClick={this.onAdd.bind(this)}>
|
||||
<Icon type="add" />
|
||||
<span>添加</span>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</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 {
|
||||
|
||||
}
|
||||
|
||||
export default class ArraySetter extends Component<
|
||||
{
|
||||
value: any[];
|
||||
field: SettingField;
|
||||
itemConfig?: {
|
||||
setter?: SetterType;
|
||||
defaultValue?: any | ((field: SettingField, editor: any) => any);
|
||||
required?: boolean;
|
||||
};
|
||||
mode?: 'popup' | 'list' | 'table';
|
||||
forceInline?: boolean;
|
||||
multiValue?: boolean;
|
||||
}> {
|
||||
render() {
|
||||
const { mode, forceInline, ...props } = this.props;
|
||||
if (mode === 'popup' || forceInline) {
|
||||
// todo popup
|
||||
return <Button>编辑数组</Button>;
|
||||
} else if (mode === 'table') {
|
||||
return <TableSetter {...props} />;
|
||||
} else {
|
||||
return <ListSetter {...props} />;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,220 @@
|
||||
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: boolean = 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;
|
||||
@ -0,0 +1,72 @@
|
||||
.lc-setter-list {
|
||||
[draggable] {
|
||||
cursor: move;
|
||||
}
|
||||
color: var(--color-text);
|
||||
|
||||
.next-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
line-height: 1 !important;
|
||||
}
|
||||
.lc-setter-list-empty {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
.next-btn {
|
||||
margin-left: 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;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.lc-listitem {
|
||||
position: relative;
|
||||
outline: none;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
height: 32px;
|
||||
|
||||
.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: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.lc-listitem-handler {
|
||||
margin-left: 2px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
.next-icon-ellipsis {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
import { Component } from "react";
|
||||
import { FieldConfig } from '../../main';
|
||||
|
||||
class ObjectSetter extends Component<{
|
||||
mode?: 'popup' | 'row' | 'form';
|
||||
forceInline?: number;
|
||||
}> {
|
||||
render() {
|
||||
const { mode, forceInline = 0 } = this.props;
|
||||
if (forceInline || (mode === 'popup' || mode === 'row')) {
|
||||
if (forceInline < 2 || mode === 'row') {
|
||||
// row
|
||||
} else {
|
||||
// popup
|
||||
}
|
||||
} else {
|
||||
// form
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ObjectSetterConfig {
|
||||
items?: FieldConfig[];
|
||||
extraConfig?: {
|
||||
setter?: SetterType;
|
||||
defaultValue?: any | ((field: SettingField, editor: any) => any);
|
||||
};
|
||||
}
|
||||
|
||||
// for table|list row
|
||||
class RowSetter extends Component<{
|
||||
config: ObjectSetterConfig;
|
||||
columnsLimit?: number;
|
||||
}> {
|
||||
render() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// form-field setter
|
||||
class FormSetter extends Component<{}> {
|
||||
|
||||
}
|
||||
@ -68,6 +68,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.lc-block-field {
|
||||
position: relative;
|
||||
>.lc-field-body>.lc-block-setter>.lc-block-setter-actions {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&.lc-accordion-field {
|
||||
// collapsed
|
||||
&.lc-field-is-collapsed {
|
||||
|
||||
@ -7,7 +7,7 @@ import './index.less';
|
||||
export interface FieldProps {
|
||||
className?: string;
|
||||
// span
|
||||
title?: TitleContent;
|
||||
title?: TitleContent | null;
|
||||
}
|
||||
|
||||
export class Field extends Component<FieldProps> {
|
||||
|
||||
@ -3,10 +3,11 @@ import { Tab, Breadcrumb, Icon } from '@alifd/next';
|
||||
import { SettingsMain, SettingField, isSettingField } from './main';
|
||||
import './style.less';
|
||||
import Title from './title';
|
||||
import SettingsTab, { registerSetter, createSetterContent, getSetter, createSettingFieldView } from './settings-tab';
|
||||
import SettingsTab, { registerSetter, createSetterContent, getSetter, createSettingFieldView } from './settings-pane';
|
||||
import Node from '../../designer/src/designer/document/node/node';
|
||||
import ArraySetter from './builtin-setters/array-setter';
|
||||
|
||||
export default class SettingsPane extends Component {
|
||||
export default class SettingsMainView extends Component {
|
||||
private main: SettingsMain;
|
||||
|
||||
constructor(props: any) {
|
||||
@ -65,7 +66,7 @@ export default class SettingsPane extends Component {
|
||||
if (this.main.isNone) {
|
||||
// 未选中节点,提示选中 或者 显示根节点设置
|
||||
return (
|
||||
<div className="lc-settings-pane">
|
||||
<div className="lc-settings-main">
|
||||
<div className="lc-settings-notice">
|
||||
<p>请在左侧画布选中节点</p>
|
||||
</div>
|
||||
@ -76,7 +77,7 @@ export default class SettingsPane extends Component {
|
||||
if (!this.main.isSame) {
|
||||
// todo: future support 获取设置项交集编辑
|
||||
return (
|
||||
<div className="lc-settings-pane">
|
||||
<div className="lc-settings-main">
|
||||
<div className="lc-settings-notice">
|
||||
<p>请选中同一类型节点编辑</p>
|
||||
</div>
|
||||
@ -87,7 +88,7 @@ export default class SettingsPane extends Component {
|
||||
const { items } = this.main;
|
||||
if (items.length > 5 || items.some(item => !isSettingField(item) || !item.isGroup)) {
|
||||
return (
|
||||
<div className="lc-settings-pane">
|
||||
<div className="lc-settings-main">
|
||||
{this.renderBreadcrumb()}
|
||||
<div className="lc-settings-body">
|
||||
<SettingsTab target={this.main} />
|
||||
@ -97,7 +98,7 @@ export default class SettingsPane extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="lc-settings-pane">
|
||||
<div className="lc-settings-main">
|
||||
<Tab
|
||||
navClassName="lc-settings-tabs"
|
||||
animation={false}
|
||||
@ -122,4 +123,6 @@ function selectNode(node: Node) {
|
||||
node.select();
|
||||
}
|
||||
|
||||
registerSetter('ArraySetter', ArraySetter);
|
||||
|
||||
export { registerSetter, createSetterContent, getSetter, createSettingFieldView };
|
||||
|
||||
@ -51,10 +51,10 @@ export interface SettingTarget {
|
||||
onEffect(action: () => void): () => void;
|
||||
|
||||
// 获取属性值
|
||||
getPropValue(propName: string): any;
|
||||
getPropValue(propName: string | number): any;
|
||||
|
||||
// 设置属性值
|
||||
setPropValue(path: string, value: any): void;
|
||||
setPropValue(propName: string | number, value: any): void;
|
||||
|
||||
/*
|
||||
// 所有属性值数据
|
||||
@ -94,6 +94,10 @@ export interface SetterConfig {
|
||||
export type SetterType = SetterConfig | string | CustomView;
|
||||
|
||||
export interface FieldExtraProps {
|
||||
/**
|
||||
* 是否必填参数
|
||||
*/
|
||||
required?: boolean;
|
||||
/**
|
||||
* default value of target prop for setter use
|
||||
*/
|
||||
@ -109,6 +113,14 @@ export interface FieldExtraProps {
|
||||
* default collapsed when display accordion
|
||||
*/
|
||||
defaultCollapsed?: boolean;
|
||||
/**
|
||||
* important field
|
||||
*/
|
||||
important?: boolean;
|
||||
/**
|
||||
* internal use
|
||||
*/
|
||||
forceInline?: number;
|
||||
}
|
||||
|
||||
export interface FieldConfig extends FieldExtraProps {
|
||||
@ -116,7 +128,7 @@ export interface FieldConfig extends FieldExtraProps {
|
||||
/**
|
||||
* the name of this setting field, which used in quickEditor
|
||||
*/
|
||||
name: string;
|
||||
name: string | number;
|
||||
/**
|
||||
* the field title
|
||||
* @default sameas .name
|
||||
@ -141,7 +153,10 @@ export class SettingField implements SettingTarget {
|
||||
readonly id = uniqueId('field');
|
||||
readonly type: 'field' | 'virtual-field' | 'group';
|
||||
readonly isGroup: boolean;
|
||||
readonly name: string;
|
||||
private _name: string | number;
|
||||
get name() {
|
||||
return this._name;
|
||||
}
|
||||
readonly title: TitleContent;
|
||||
readonly editor: any;
|
||||
readonly extraProps: FieldExtraProps;
|
||||
@ -153,13 +168,19 @@ export class SettingField implements SettingTarget {
|
||||
readonly nodes: Node[];
|
||||
readonly componentType: ComponentType | null;
|
||||
readonly designer: Designer;
|
||||
readonly path: string[];
|
||||
get path() {
|
||||
const path = this.parent.path.slice();
|
||||
if (this.type === 'field') {
|
||||
path.push(String(this.name));
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
constructor(readonly parent: SettingTarget, config: FieldConfig) {
|
||||
const { type, title, name, items, setter, extraProps, ...rest } = config;
|
||||
|
||||
if (type == null) {
|
||||
const c = name.substr(0, 1);
|
||||
const c = typeof name === 'string' ? name.substr(0, 1) : '';
|
||||
if (c === '#') {
|
||||
this.type = 'group';
|
||||
} else if (c === '!') {
|
||||
@ -171,8 +192,8 @@ export class SettingField implements SettingTarget {
|
||||
this.type = type;
|
||||
}
|
||||
// initial self properties
|
||||
this.name = name;
|
||||
this.title = title || name;
|
||||
this._name = name;
|
||||
this.title = title || String(name);
|
||||
this.setter = setter;
|
||||
this.extraProps = {
|
||||
...rest,
|
||||
@ -189,10 +210,6 @@ export class SettingField implements SettingTarget {
|
||||
this.isOne = parent.isOne;
|
||||
this.isNone = parent.isNone;
|
||||
this.designer = parent.designer!;
|
||||
this.path = parent.path.slice();
|
||||
if (this.type === 'field') {
|
||||
this.path.push(this.name);
|
||||
}
|
||||
|
||||
// initial items
|
||||
if (this.type === 'group' && items) {
|
||||
@ -219,30 +236,41 @@ export class SettingField implements SettingTarget {
|
||||
this._items = [];
|
||||
}
|
||||
|
||||
createField(config: FieldConfig): SettingField {
|
||||
return new SettingField(this, config);
|
||||
}
|
||||
|
||||
get items() {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
// ====== 当前属性读写 =====
|
||||
|
||||
// Todo cache!!
|
||||
/**
|
||||
* 判断当前属性值是否一致
|
||||
* 0 无值/多种值
|
||||
* 1 类似值,比如数组长度一样
|
||||
* 2 单一植
|
||||
*/
|
||||
get isSameValue(): boolean {
|
||||
get valueState(): number {
|
||||
if (this.type !== 'field') {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
const propName = this.path.join('.');
|
||||
const first = this.nodes[0].getProp(propName)!;
|
||||
let l = this.nodes.length;
|
||||
let state = 2;
|
||||
while (l-- > 1) {
|
||||
const next = this.nodes[l].getProp(propName, false);
|
||||
if (!first.isEqual(next)) {
|
||||
return false;
|
||||
const s = first.compare(next);
|
||||
if (s > 1) {
|
||||
return 0;
|
||||
}
|
||||
if (s === 1) {
|
||||
state = 1;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -252,6 +280,11 @@ export class SettingField implements SettingTarget {
|
||||
if (this.type !== 'field') {
|
||||
return null;
|
||||
}
|
||||
// todo: use getValue
|
||||
const { getValue } = this.extraProps;
|
||||
if (getValue) {
|
||||
return getValue(this, this.editor);
|
||||
}
|
||||
return this.parent.getPropValue(this.name);
|
||||
}
|
||||
|
||||
@ -262,13 +295,37 @@ export class SettingField implements SettingTarget {
|
||||
if (this.type !== 'field') {
|
||||
return;
|
||||
}
|
||||
// todo: use onChange
|
||||
this.parent.setPropValue(this.name, val);
|
||||
}
|
||||
|
||||
setKey(key: string | number) {
|
||||
if (this.type !== 'field') {
|
||||
return;
|
||||
}
|
||||
const propName = this.path.join('.');
|
||||
let l = this.nodes.length;
|
||||
while (l-- > 1) {
|
||||
this.nodes[l].getProp(propName, true)!.key = key;
|
||||
}
|
||||
this._name = key;
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (this.type !== 'field') {
|
||||
return;
|
||||
}
|
||||
const propName = this.path.join('.');
|
||||
let l = this.nodes.length;
|
||||
while (l-- > 1) {
|
||||
this.nodes[l].getProp(propName)?.remove()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置子级属性值
|
||||
*/
|
||||
setPropValue(propName: string, value: any) {
|
||||
setPropValue(propName: string | number, value: any) {
|
||||
const path = this.type === 'field' ? `${this.name}.${propName}` : propName;
|
||||
this.parent.setPropValue(path, value);
|
||||
}
|
||||
@ -276,19 +333,11 @@ export class SettingField implements SettingTarget {
|
||||
/**
|
||||
* 获取子级属性值
|
||||
*/
|
||||
getPropValue(propName: string): any {
|
||||
getPropValue(propName: string | number): any {
|
||||
const path = this.type === 'field' ? `${this.name}.${propName}` : propName;
|
||||
return this.parent.getPropValue(path);
|
||||
}
|
||||
|
||||
// 添加
|
||||
// addItem(config: FieldConfig): SettingField {}
|
||||
// 删除
|
||||
// deleteItem() {}
|
||||
// 移动
|
||||
// insertItem(item: SettingField, index?: number) {}
|
||||
// remove() {}
|
||||
|
||||
purge() {
|
||||
this.disposeItems();
|
||||
}
|
||||
@ -298,6 +347,7 @@ export function isSettingField(obj: any): obj is SettingField {
|
||||
return obj && obj.isSettingField;
|
||||
}
|
||||
|
||||
|
||||
export class SettingsMain implements SettingTarget {
|
||||
private emitter = new EventEmitter();
|
||||
|
||||
@ -396,18 +446,7 @@ export class SettingsMain implements SettingTarget {
|
||||
* 获取属性值
|
||||
*/
|
||||
getPropValue(propName: string): any {
|
||||
if (!this.isSame) {
|
||||
return null;
|
||||
}
|
||||
const first = this.nodes[0].getProp(propName)!;
|
||||
let l = this.nodes.length;
|
||||
while (l-- > 1) {
|
||||
const next = this.nodes[l].getProp(propName, false);
|
||||
if (!first.isEqual(next)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return first.value;
|
||||
return this.nodes[0].getProp(propName, false)?.value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -79,11 +79,13 @@ class SettingFieldView extends Component<{ field: SettingField }> {
|
||||
...(typeof setterProps === 'function' ? setterProps(field, editor) : setterProps),
|
||||
};
|
||||
if (field.type === 'field') {
|
||||
state.value = field.getValue();
|
||||
if (defaultValue != null && !('defaultValue' in state.setterProps)) {
|
||||
state.setterProps.defaultValue = defaultValue;
|
||||
}
|
||||
if (!field.isSameValue) {
|
||||
if (field.valueState > 0) {
|
||||
state.value = field.getValue();
|
||||
} else {
|
||||
state.value = null;
|
||||
state.setterProps.multiValue = true;
|
||||
if (!('placeholder' in props)) {
|
||||
state.setterProps.placeholder = '多种值';
|
||||
@ -118,18 +120,20 @@ class SettingFieldView extends Component<{ field: SettingField }> {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { field } = this.props;
|
||||
const { visible, value, setterProps } = this.state;
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
const { field } = this.props;
|
||||
const { title, extraProps } = field;
|
||||
|
||||
// todo: error handling
|
||||
|
||||
return (
|
||||
<Field title={field.title}>
|
||||
<Field title={extraProps.forceInline ? null : title}>
|
||||
{createSetterContent(this.setterType, {
|
||||
...setterProps,
|
||||
forceInline: extraProps.forceInline,
|
||||
key: field.id,
|
||||
// === injection
|
||||
prop: field,
|
||||
@ -204,7 +208,7 @@ class SettingGroupView extends Component<{ field: SettingField }> {
|
||||
}
|
||||
}
|
||||
|
||||
export function createSettingFieldView(item: SettingField | CustomView, field: SettingTarget, index: number) {
|
||||
export function createSettingFieldView(item: SettingField | CustomView, field: SettingTarget, index?: number) {
|
||||
if (isSettingField(item)) {
|
||||
if (item.isGroup) {
|
||||
return <SettingGroupView field={item} key={item.id} />;
|
||||
@ -216,7 +220,11 @@ export function createSettingFieldView(item: SettingField | CustomView, field: S
|
||||
}
|
||||
}
|
||||
|
||||
export default class SettingsTab extends Component<{ target: SettingTarget }> {
|
||||
export function showPopup() {
|
||||
|
||||
}
|
||||
|
||||
export default class SettingsPane extends Component<{ target: SettingTarget }> {
|
||||
state: { items: Array<SettingField | CustomView> } = {
|
||||
items: [],
|
||||
};
|
||||
@ -254,7 +262,7 @@ export default class SettingsTab extends Component<{ target: SettingTarget }> {
|
||||
const { items } = this.state;
|
||||
const { target } = this.props;
|
||||
return (
|
||||
<div className="lc-settings-singlepane">
|
||||
<div className="lc-settings-pane">
|
||||
{items.map((item, index) => createSettingFieldView(item, target, index))}
|
||||
</div>
|
||||
);
|
||||
@ -31,7 +31,7 @@
|
||||
--color-block-background-deep-dark: #BAC3CC;
|
||||
}
|
||||
|
||||
.lc-settings-pane {
|
||||
.lc-settings-main {
|
||||
position: relative;
|
||||
|
||||
.lc-settings-notice {
|
||||
@ -77,7 +77,7 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.lc-settings-singlepane {
|
||||
.lc-settings-pane {
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user