can change props

This commit is contained in:
kangwei 2020-03-06 00:39:09 +08:00
parent 9c52978702
commit 38f7c6d5cc
10 changed files with 232 additions and 56 deletions

View File

@ -147,18 +147,17 @@ export class ComponentType {
name: '#props', name: '#props',
title: "属性", title: "属性",
items: [{ items: [{
name: 'title', name: 'label',
title: '标', title: '标',
setter: 'StringSetter' setter: 'StringSetter'
}, { }, {
name: 'description', name: 'name',
title: '描述', title: '名称',
setter: { setter: 'StringSetter'
componentName: 'StringSetter', }, {
props: { name: 'size',
multiline: true, title: '大小',
} setter: 'StringSetter'
}
}] }]
}, { }, {
name: '#styles', name: '#styles',
@ -188,12 +187,6 @@ export class ComponentType {
name: '#data', name: '#data',
title: "数据", title: "数据",
items: [] items: []
}, {
name: '#a',
title: "数据1",
}, {
name: '#b',
title: "数据2",
}]; }];
} }
@ -201,7 +194,7 @@ export class ComponentType {
private childWhitelist?: string[] | null; private childWhitelist?: string[] | null;
get title() { get title() {
return this._spec.title; return this._spec.title || this.componentName;
} }
get icon() { get icon() {
@ -261,7 +254,6 @@ export class ComponentType {
return this._spec; return this._spec;
} }
checkNestingUp(my: Node | NodeData, parent: NodeParent) { checkNestingUp(my: Node | NodeData, parent: NodeParent) {
if (this.parentWhitelist) { if (this.parentWhitelist) {
return this.parentWhitelist.includes(parent.componentName); return this.parentWhitelist.includes(parent.componentName);

View File

@ -228,15 +228,25 @@ export default class Designer {
} }
@obx.val private _componentTypesMap = new Map<string, ComponentType>(); @obx.val private _componentTypesMap = new Map<string, ComponentType>();
private _lostComponentTypesMap = new Map<string, ComponentType>();
private buildComponentTypesMap(specs: ComponentDescription[]) { private buildComponentTypesMap(specs: ComponentDescription[]) {
specs.forEach(spec => { specs.forEach(spec => {
const key = spec.componentName; const key = spec.componentName;
const had = this._componentTypesMap.get(key); let cType = this._componentTypesMap.get(key);
if (had) { if (cType) {
had.spec = spec; cType.spec = spec;
} else { } else {
this._componentTypesMap.set(key, new ComponentType(spec)); cType = this._lostComponentTypesMap.get(key);
if (cType) {
cType.spec = spec;
this._lostComponentTypesMap.delete(key);
} else {
cType = new ComponentType(spec);
}
this._componentTypesMap.set(key, cType);
} }
}); });
} }
@ -246,9 +256,17 @@ export default class Designer {
return this._componentTypesMap.get(componentName)!; return this._componentTypesMap.get(componentName)!;
} }
return new ComponentType({ if (this._lostComponentTypesMap.has(componentName)) {
return this._lostComponentTypesMap.get(componentName)!;
}
const cType = new ComponentType({
componentName, componentName,
}); });
this._lostComponentTypesMap.set(componentName, cType);
return cType;
} }
get componentsMap(): { [key: string]: ComponentDescription } { get componentsMap(): { [key: string]: ComponentDescription } {

View File

@ -98,7 +98,7 @@ export default class Node {
return v; return v;
} }
} }
return this.componentName; return this.componentType.title;
} }
get isSlotRoot(): boolean { get isSlotRoot(): boolean {
@ -173,6 +173,17 @@ export default class Node {
this.document.selection.select(this.id); this.document.selection.select(this.id);
} }
/**
*
*/
hover(flag: boolean = true) {
if (flag) {
this.document.designer.hovering.hover(this);
} else {
this.document.designer.hovering.unhover(this);
}
}
/** /**
* *
*/ */

View File

@ -29,7 +29,6 @@ export default class PropStash implements IPropParent {
} }
} }
if (pending.length > 0) { if (pending.length > 0) {
debugger;
untracked(() => { untracked(() => {
for (const item of pending) { for (const item of pending) {
write(item); write(item);

View File

@ -77,6 +77,22 @@ export default class Prop implements IPropParent {
return null; return null;
} }
/**
*
*/
@computed get code() {
if (isJSExpression(this.value)) {
return this.value.value;
}
if (this.type === 'slot') {
return JSON.stringify(this._slotNode!.export(false));
}
return JSON.stringify(this.value);
}
set code(val) {
}
@computed getAsString(): string { @computed getAsString(): string {
if (this.type === 'literal') { if (this.type === 'literal') {
return this._value ? String(this._value) : ''; return this._value ? String(this._value) : '';
@ -167,6 +183,17 @@ export default class Prop implements IPropParent {
return this._type === 'unset'; return this._type === 'unset';
} }
isEqual(otherProp: Prop | null): boolean {
if (!otherProp) {
return this.isUnset();
}
if (otherProp.type !== this.type) {
return false;
}
// 'unset' | 'literal' | 'map' | 'list' | 'expression' | 'slot'
return this.code === otherProp.code;
}
/** /**
* JS * JS
* JSExpresion | JSSlot * JSExpresion | JSSlot
@ -278,6 +305,7 @@ export default class Prop implements IPropParent {
get(path: string): Prop; get(path: string): Prop;
get(path: string, stash = true) { get(path: string, stash = true) {
const type = this._type; const type = this._type;
// todo: support list get
if (type !== 'map' && type !== 'unset' && !stash) { if (type !== 'map' && type !== 'unset' && !stash) {
return null; return null;
} }

View File

@ -24,6 +24,12 @@ export default class Hovering {
this._current = node; this._current = node;
} }
unhover(node: Node) {
if (this._current === node) {
this._current = null;
}
}
leave(document: DocumentModel) { leave(document: DocumentModel) {
if (this.current && this.current.document === document) { if (this.current && this.current.document === document) {
this._current = null; this._current = null;

View File

@ -4,6 +4,7 @@ import { SettingsMain, SettingField, isSettingField } from './main';
import './style.less'; import './style.less';
import Title from './title'; import Title from './title';
import SettingsTab, { registerSetter, createSetterContent, getSetter, createSettingFieldView } from './settings-tab'; import SettingsTab, { registerSetter, createSetterContent, getSetter, createSettingFieldView } from './settings-tab';
import Node from '../../designer/src/designer/document/node/node';
export default class SettingsPane extends Component { export default class SettingsPane extends Component {
private main: SettingsMain; private main: SettingsMain;
@ -28,20 +29,34 @@ export default class SettingsPane extends Component {
if (this.main.isMulti) { if (this.main.isMulti) {
return ( return (
<div className="lc-settings-navigator"> <div className="lc-settings-navigator">
{this.main.componentType ? this.main.componentType.icon : <Icon type="ellipsis" />} {this.main.componentType!.icon || <Icon type="ellipsis" size="small" />}
<span></span> <span>
{this.main.componentType!.title} x {this.main.nodes.length}
</span>
</div> </div>
); );
} }
let node: Node | null = this.main.nodes[0]!;
const items = [];
let l = 4;
while (l-- > 0 && node) {
const props =
l === 3
? {}
: {
onMouseOver: hoverNode.bind(null, node, true),
onMouseOut: hoverNode.bind(null, node, false),
onClick: selectNode.bind(null, node),
};
items.unshift(<Breadcrumb.Item {...props}>{node.title}</Breadcrumb.Item>);
node = node.parent;
}
return ( return (
<div className="lc-settings-navigator"> <div className="lc-settings-navigator">
{this.main.componentType ? this.main.componentType.icon : <Icon type="ellipsis" />} {this.main.componentType!.icon || <Icon type="ellipsis" size="small" />}
<Breadcrumb> <Breadcrumb className="lc-settings-node-breadcrumb">{items}</Breadcrumb>
<Breadcrumb.Item></Breadcrumb.Item>
<Breadcrumb.Item></Breadcrumb.Item>
<Breadcrumb.Item></Breadcrumb.Item>
</Breadcrumb>
</div> </div>
); );
} }
@ -49,12 +64,24 @@ export default class SettingsPane extends Component {
render() { render() {
if (this.main.isNone) { if (this.main.isNone) {
// 未选中节点,提示选中 或者 显示根节点设置 // 未选中节点,提示选中 或者 显示根节点设置
return <div className="lc-settings-pane"></div>; return (
<div className="lc-settings-pane">
<div className="lc-settings-notice">
<p></p>
</div>
</div>
);
} }
if (!this.main.isSame) { if (!this.main.isSame) {
// todo: future support 获取设置项交集编辑 // todo: future support 获取设置项交集编辑
return <div className="lc-settings-pane"></div>; return (
<div className="lc-settings-pane">
<div className="lc-settings-notice">
<p></p>
</div>
</div>
);
} }
const { items } = this.main; const { items } = this.main;
@ -79,7 +106,7 @@ export default class SettingsPane extends Component {
> >
{(items as SettingField[]).map(field => ( {(items as SettingField[]).map(field => (
<Tab.Item className="lc-settings-tab-item" title={<Title title={field.title} />} key={field.name}> <Tab.Item className="lc-settings-tab-item" title={<Title title={field.title} />} key={field.name}>
<SettingsTab target={field} /> <SettingsTab target={field} key={field.id} />
</Tab.Item> </Tab.Item>
))} ))}
</Tab> </Tab>
@ -88,4 +115,11 @@ export default class SettingsPane extends Component {
} }
} }
function hoverNode(node: Node, flag: boolean) {
node.hover(flag);
}
function selectNode(node: Node) {
node.select();
}
export { registerSetter, createSetterContent, getSetter, createSettingFieldView }; export { registerSetter, createSetterContent, getSetter, createSettingFieldView };

View File

@ -43,24 +43,28 @@ export interface SettingTarget {
readonly designer?: Designer; readonly designer?: Designer;
readonly path: string[];
/** /**
* *
*/ */
onEffect(action: () => void): () => void; onEffect(action: () => void): () => void;
// 获取属性值
getPropValue(propName: string): any;
// 设置属性值
setPropValue(path: string, value: any): void;
/* /*
// 所有属性值数据 // 所有属性值数据
readonly props: object; readonly props: object;
// 获取属性值
getPropValue(propName: string): any;
// 设置多个属性值,替换原有值 // 设置多个属性值,替换原有值
setProps(data: object): void; setProps(data: object): void;
// 设置多个属性值,和原有值合并 // 设置多个属性值,和原有值合并
mergeProps(data: object): void; mergeProps(data: object): void;
// 绑定属性值发生变化时 // 绑定属性值发生变化时
onPropsChange(fn: () => void): () => void; onPropsChange(fn: () => void): () => void;
// 设置属性值
setPropValue(path: string, value: any) {}
*/ */
} }
@ -149,8 +153,9 @@ export class SettingField implements SettingTarget {
readonly nodes: Node[]; readonly nodes: Node[];
readonly componentType: ComponentType | null; readonly componentType: ComponentType | null;
readonly designer: Designer; readonly designer: Designer;
readonly path: string[];
constructor(readonly parent: SettingTarget, private config: FieldConfig) { constructor(readonly parent: SettingTarget, config: FieldConfig) {
const { type, title, name, items, setter, extraProps, ...rest } = config; const { type, title, name, items, setter, extraProps, ...rest } = config;
if (type == null) { if (type == null) {
@ -184,6 +189,10 @@ export class SettingField implements SettingTarget {
this.isOne = parent.isOne; this.isOne = parent.isOne;
this.isNone = parent.isNone; this.isNone = parent.isNone;
this.designer = parent.designer!; this.designer = parent.designer!;
this.path = parent.path.slice();
if (this.type === 'field') {
this.path.push(this.name);
}
// initial items // initial items
if (this.type === 'group' && items) { if (this.type === 'group' && items) {
@ -210,36 +219,44 @@ export class SettingField implements SettingTarget {
this._items = []; this._items = [];
} }
get isSameValue() {
return true;
}
get items() { get items() {
return this._items; return this._items;
} }
get prop(): SettingTargetProp | void { // ====== 当前属性读写 =====
if (this.type === 'field') {
} get isSameValue(): boolean {
return; // todo:
return true;
} }
getValue(): any { getValue(): any {
return null; return this.parent.getPropValue(this.name);
} }
setValue(val: any) { setValue(val: any) {
this.parent.setPropValue(this.name, val);
}
// 设置属性值
setPropValue(propName: string, value: any) {
const path = this.type === 'field' ? `${this.name}.${propName}` : propName;
this.parent.setPropValue(path, value);
}
// 获取属性值
getPropValue(propName: string): any {
const path = this.type === 'field' ? `${this.name}.${propName}` : propName;
return this.parent.getPropValue(path);
} }
// 添加 // 添加
// addItem(config: FieldConfig): SettingField {} // addItem(config: FieldConfig): SettingField {}
// 删除 // 删除
deleteItem() {} // deleteItem() {}
// 移动 // 移动
insertItem(item: SettingField, index?: number) {} // insertItem(item: SettingField, index?: number) {}
remove() {} // remove() {}
purge() { purge() {
this.disposeItems(); this.disposeItems();
@ -250,8 +267,6 @@ export function isSettingField(obj: any): obj is SettingField {
return obj && obj.isSettingField; return obj && obj.isSettingField;
} }
export class SettingTargetProp {}
export class SettingsMain implements SettingTarget { export class SettingsMain implements SettingTarget {
private emitter = new EventEmitter(); private emitter = new EventEmitter();
@ -260,6 +275,7 @@ export class SettingsMain implements SettingTarget {
private _sessionId = ''; private _sessionId = '';
private _componentType: ComponentType | null = null; private _componentType: ComponentType | null = null;
private _isSame: boolean = true; private _isSame: boolean = true;
readonly path = [];
get nodes(): Node[] { get nodes(): Node[] {
return this._nodes; return this._nodes;
@ -345,6 +361,47 @@ export class SettingsMain implements SettingTarget {
return this.onNodesChange(action); return this.onNodesChange(action);
} }
/**
*
*/
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;
}
/**
*
*/
setPropValue(propName: string, value: any) {
this.nodes.forEach(node => {
node.setPropValue(propName, value);
});
}
// 设置多个属性值,替换原有值
setProps(data: object) {
this.nodes.forEach(node => {
node.setProps(data as any);
});
}
// 设置多个属性值,和原有值合并
mergeProps(data: object) {
this.nodes.forEach(node => {
node.mergeProps(data as any);
});
}
private setup(nodes: Node[]) { private setup(nodes: Node[]) {
this._nodes = nodes; this._nodes = nodes;
@ -394,6 +451,9 @@ export class SettingsMain implements SettingTarget {
if (theSame) { if (theSame) {
this._isSame = true; this._isSame = true;
this._componentType = type; this._componentType = type;
} else {
this._isSame = false;
this._componentType = null;
} }
} }

View File

@ -60,7 +60,7 @@ class SettingFieldView extends Component<{ field: SettingField }> {
render() { render() {
const { field } = this.props; const { field } = this.props;
const { setter, title, extraProps, isSameValue } = field; const { setter, title, extraProps } = field;
const { defaultValue } = extraProps; const { defaultValue } = extraProps;
const { visible, value } = this.state; const { visible, value } = this.state;
// reaction point // reaction point
@ -77,9 +77,11 @@ class SettingFieldView extends Component<{ field: SettingField }> {
if (defaultValue != null && !('defaultValue' in props)) { if (defaultValue != null && !('defaultValue' in props)) {
props.defaultValue = defaultValue; props.defaultValue = defaultValue;
} }
/*
if (!('placeholder' in props) && !isSameValue) { if (!('placeholder' in props) && !isSameValue) {
props.placeholder = '多种值'; props.placeholder = '多种值';
} }
*/
// todo: error handling // todo: error handling

View File

@ -34,12 +34,38 @@
.lc-settings-pane { .lc-settings-pane {
position: relative; position: relative;
.lc-settings-notice {
text-align: center;
font-size: 12px;
font-family: PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica,Arial,sans-serif;
color: var(--color-text ,rgba(0,0,0,.6));
margin: 50px 15px 0;
overflow: hidden;
padding: 15px 0;
}
.lc-settings-navigator { .lc-settings-navigator {
height: 30px; height: 30px;
display: flex; display: flex;
align-items: center; align-items: center;
padding-left: 5px; padding-left: 5px;
border-bottom: 1px solid var(--color-line-normal); border-bottom: 1px solid var(--color-line-normal);
.lc-settings-node-breadcrumb {
margin-left: 5px;
.next-breadcrumb {
display: inline-flex;
align-items: stretch;
height: 24px;
}
.next-breadcrumb-item {
display: inline-flex;
align-items: center;
cursor: default;
&:not(:last-child):hover {
cursor: pointer;
}
}
}
} }
.lc-settings-body { .lc-settings-body {