mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-14 13:03:07 +00:00
Merge branch 'feat/joint-editor' of gitlab.alibaba-inc.com:ali-lowcode/ali-lowcode-engine into feat/joint-editor
This commit is contained in:
commit
b55f6aa927
@ -218,18 +218,27 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
||||
// 事件路由
|
||||
doc.addEventListener('mousedown', (downEvent: MouseEvent) => {
|
||||
const nodeInst = this.getNodeInstanceFromElement(downEvent.target as Element);
|
||||
if (!nodeInst?.node) {
|
||||
selection.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const node = nodeInst?.node || this.document.rootNode;
|
||||
const isMulti = downEvent.metaKey || downEvent.ctrlKey;
|
||||
const isLeftButton = downEvent.which === 1 || downEvent.button === 0;
|
||||
const checkSelect = (e: MouseEvent) => {
|
||||
doc.removeEventListener('mouseup', checkSelect, true);
|
||||
if (!isShaken(downEvent, e)) {
|
||||
const id = node.id;
|
||||
designer.activeTracker.track(node);
|
||||
if (isMulti && !isRootNode(node) && selection.has(id)) {
|
||||
selection.remove(id);
|
||||
} else {
|
||||
selection.select(id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (isLeftButton) {
|
||||
const node: Node = nodeInst.node;
|
||||
if (isLeftButton && !isRootNode(node)) {
|
||||
let nodes: Node[] = [node];
|
||||
let ignoreUpSelected = false;
|
||||
// 排除根节点拖拽
|
||||
selection.remove(this.document.rootNode.id);
|
||||
if (isMulti) {
|
||||
// multi select mode, directily add
|
||||
if (!selection.has(node.id)) {
|
||||
@ -257,20 +266,6 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
||||
}
|
||||
}
|
||||
|
||||
const checkSelect = (e: MouseEvent) => {
|
||||
doc.removeEventListener('mouseup', checkSelect, true);
|
||||
if (!isShaken(downEvent, e)) {
|
||||
// const node = hasConditionFlow(target) ? target.conditionFlow : target;
|
||||
const node = nodeInst.node!;
|
||||
const id = node.id;
|
||||
designer.activeTracker.track(node);
|
||||
if (isMulti && selection.has(id)) {
|
||||
selection.remove(id);
|
||||
} else {
|
||||
selection.select(id);
|
||||
}
|
||||
}
|
||||
};
|
||||
doc.addEventListener('mouseup', checkSelect, true);
|
||||
});
|
||||
|
||||
|
||||
@ -154,23 +154,56 @@ export class ComponentType {
|
||||
setter: 'StringSetter',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
title: '名称',
|
||||
name: 'data',
|
||||
title: '数据',
|
||||
setter: {
|
||||
componentName: 'ArraySetter',
|
||||
props: {
|
||||
itemConfig: {
|
||||
setter: 'StringSetter',
|
||||
defaultValue: '',
|
||||
setter: {
|
||||
componentName: 'ObjectSetter',
|
||||
props: {
|
||||
config: {
|
||||
items: [
|
||||
{
|
||||
name: 'title',
|
||||
title: '名称',
|
||||
setter: 'StringSetter',
|
||||
important: true,
|
||||
},
|
||||
{
|
||||
name: 'records',
|
||||
title: '记录集',
|
||||
setter: {
|
||||
componentName: 'ArraySetter',
|
||||
props: {
|
||||
itemConfig: {
|
||||
setter: {
|
||||
componentName: 'ArraySetter',
|
||||
props: {
|
||||
itemConfig: {
|
||||
setter: 'StringSetter',
|
||||
defaultValue: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultValue: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
important: true,
|
||||
},
|
||||
],
|
||||
extraConfig: {},
|
||||
},
|
||||
// mode: 'popup'
|
||||
},
|
||||
},
|
||||
defaultValue: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
title: '大小',
|
||||
setter: 'StringSetter',
|
||||
},
|
||||
{
|
||||
name: 'age',
|
||||
title: '年龄',
|
||||
|
||||
@ -76,7 +76,7 @@ export default class Prop implements IPropParent {
|
||||
}
|
||||
return this.items!.map(prop => {
|
||||
const v = prop.export(serialize);
|
||||
return v === UNSET ? null : v
|
||||
return v === UNSET ? null : v;
|
||||
});
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ export default class Prop implements IPropParent {
|
||||
return JSON.stringify(this.value);
|
||||
}
|
||||
set code(val) {
|
||||
|
||||
// todo
|
||||
}
|
||||
|
||||
@computed getAsString(): string {
|
||||
@ -205,6 +205,7 @@ export default class Prop implements IPropParent {
|
||||
if (this.type === 'list') {
|
||||
return this.size === other.size ? 1 : 2;
|
||||
}
|
||||
|
||||
// 'literal' | 'map' | 'expression' | 'slot'
|
||||
return this.code === other.code ? 0 : 2;
|
||||
}
|
||||
@ -548,7 +549,7 @@ export function isProp(obj: any): obj is Prop {
|
||||
return obj && obj.isProp;
|
||||
}
|
||||
|
||||
function isValidArrayIndex(key: any, limit: number = -1): key is number {
|
||||
function isValidArrayIndex(key: any, limit = -1): key is number {
|
||||
const n = parseFloat(String(key));
|
||||
return n >= 0 && Math.floor(n) === n && isFinite(n) && (limit < 0 || n < limit);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { obx, computed } from '@recore/obx';
|
||||
import { INodeSelector, IViewport } from '../simulator';
|
||||
import { uniqueId } from '../../../../utils/unique-id';
|
||||
import { isRootNode } from '../document/node/root-node';
|
||||
|
||||
export default class OffsetObserver {
|
||||
readonly id = uniqueId('oobx');
|
||||
@ -17,25 +18,25 @@ export default class OffsetObserver {
|
||||
@obx hasOffset = false;
|
||||
@computed get offsetLeft() {
|
||||
if (!this.viewport.scrolling || this.lastOffsetLeft == null) {
|
||||
this.lastOffsetLeft = (this.left + this.viewport.scrollX) * this.scale;
|
||||
this.lastOffsetLeft = this.isRoot ? this.viewport.scrollX : (this.left + this.viewport.scrollX) * this.scale;
|
||||
}
|
||||
return this.lastOffsetLeft;
|
||||
}
|
||||
@computed get offsetTop() {
|
||||
if (!this.viewport.scrolling || this.lastOffsetTop == null) {
|
||||
this.lastOffsetTop = (this.top + this.viewport.scrollY) * this.scale;
|
||||
this.lastOffsetTop = this.isRoot ? this.viewport.scrollY : (this.top + this.viewport.scrollY) * this.scale;
|
||||
}
|
||||
return this.lastOffsetTop;
|
||||
}
|
||||
@computed get offsetHeight() {
|
||||
if (!this.viewport.scrolling || this.lastOffsetHeight == null) {
|
||||
this.lastOffsetHeight = this.height * this.scale;
|
||||
this.lastOffsetHeight = this.isRoot ? this.viewport.height : this.height * this.scale;
|
||||
}
|
||||
return this.lastOffsetHeight;
|
||||
}
|
||||
@computed get offsetWidth() {
|
||||
if (!this.viewport.scrolling || this.lastOffsetWidth == null) {
|
||||
this.lastOffsetWidth = this.width * this.scale;
|
||||
this.lastOffsetWidth = this.isRoot ? this.viewport.width : this.width * this.scale;
|
||||
}
|
||||
return this.lastOffsetWidth;
|
||||
}
|
||||
@ -46,12 +47,18 @@ export default class OffsetObserver {
|
||||
|
||||
private pid: number | undefined;
|
||||
private viewport: IViewport;
|
||||
private isRoot: boolean;
|
||||
|
||||
constructor(readonly nodeInstance: INodeSelector) {
|
||||
const { node, instance } = nodeInstance;
|
||||
const doc = node.document;
|
||||
const host = doc.simulator!;
|
||||
this.isRoot = isRootNode(node);
|
||||
this.viewport = host.viewport;
|
||||
if (this.isRoot) {
|
||||
this.hasOffset = true;
|
||||
return;
|
||||
}
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1 +1,15 @@
|
||||
属性面板
|
||||
|
||||
其中 field 可独立出去一个包
|
||||
|
||||
提供:
|
||||
1. 快捷设置面板服务
|
||||
2. 对应节点的设置面板服务
|
||||
3. 右侧设置面板
|
||||
4. 提供 setters 服务,setters 注册、获取机制
|
||||
|
||||
依赖:
|
||||
|
||||
tip 处理
|
||||
field
|
||||
popup
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { Component } from 'react';
|
||||
import { Component, Fragment } from 'react';
|
||||
import { Icon, Button, Message } from '@alifd/next';
|
||||
import Sortable from './sortable';
|
||||
import { SettingField, SetterType } from '../../main';
|
||||
import { SettingField, SetterType, FieldConfig } from '../../main';
|
||||
import './style.less';
|
||||
import { createSettingFieldView } from '../../settings-pane';
|
||||
import { PopupContext, PopupPipe } from '../../popup';
|
||||
import Title from '../../title';
|
||||
|
||||
interface ArraySetterState {
|
||||
items: SettingField[];
|
||||
@ -45,7 +47,7 @@ export class ListSetter extends Component<ArraySetterProps, ArraySetterState> {
|
||||
const item = field.createField({
|
||||
...props.itemConfig,
|
||||
name: i,
|
||||
forceInline: 1,
|
||||
forceInline: 2,
|
||||
});
|
||||
items[i] = item;
|
||||
itemsMap.set(item.id, item);
|
||||
@ -140,37 +142,39 @@ export class ListSetter extends Component<ArraySetterProps, ArraySetterState> {
|
||||
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">
|
||||
{/*<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>*/}
|
||||
{content}
|
||||
<Button className="lc-setter-list-add" type="primary" onClick={this.onAdd.bind(this)}>
|
||||
<Icon type="add" />
|
||||
<span>添加一项</span>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -208,7 +212,11 @@ class ArrayItem extends Component<{
|
||||
}
|
||||
}
|
||||
|
||||
class TableSetter extends ListSetter {}
|
||||
class TableSetter extends ListSetter {
|
||||
// todo:
|
||||
// forceInline = 1
|
||||
// has more actions
|
||||
}
|
||||
|
||||
export default class ArraySetter extends Component<{
|
||||
value: any[];
|
||||
@ -218,17 +226,52 @@ export default class ArraySetter extends Component<{
|
||||
defaultValue?: any | ((field: SettingField) => any);
|
||||
required?: boolean;
|
||||
};
|
||||
mode?: 'popup' | 'list' | 'table';
|
||||
mode?: 'popup' | 'list';
|
||||
forceInline?: boolean;
|
||||
multiValue?: boolean;
|
||||
}> {
|
||||
static contextType = PopupContext;
|
||||
private pipe: any;
|
||||
render() {
|
||||
const { mode, forceInline, ...props } = this.props;
|
||||
const { field, itemConfig } = props;
|
||||
if (mode === 'popup' || forceInline) {
|
||||
// todo popup
|
||||
return <Button>编辑数组</Button>;
|
||||
} else if (mode === 'table') {
|
||||
return <TableSetter {...props} />;
|
||||
const title = (
|
||||
<Fragment>
|
||||
编辑:
|
||||
<Title title={field.title} />
|
||||
</Fragment>
|
||||
);
|
||||
if (!this.pipe) {
|
||||
let width = 360;
|
||||
const setter: any = itemConfig?.setter;
|
||||
if (setter?.componentName === 'ObjectSetter') {
|
||||
const items: FieldConfig[] = setter.props?.config?.items;
|
||||
if (items && Array.isArray(items)) {
|
||||
const length = items.filter(item => item.required || item.important).length;
|
||||
if (length === 3) {
|
||||
width = 480;
|
||||
} else if (length > 3) {
|
||||
width = 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.pipe = (this.context as PopupPipe).create({ width });
|
||||
}
|
||||
this.pipe.send(
|
||||
<TableSetter key={field.id} {...props} />,
|
||||
title,
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
onClick={e => {
|
||||
this.pipe.show((e as any).target);
|
||||
}}
|
||||
>
|
||||
<Icon type="edit" />
|
||||
{forceInline ? title : '编辑数组'}
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
return <ListSetter {...props} />;
|
||||
}
|
||||
|
||||
@ -8,13 +8,14 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
line-height: 1 !important;
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.lc-setter-list-empty {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
.next-btn {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.lc-setter-list-add {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 8px;;
|
||||
}
|
||||
|
||||
.lc-setter-list-scroll-body {
|
||||
@ -28,14 +29,16 @@
|
||||
border: 1px solid rgba(31,56,88,.2);
|
||||
background-color: var(--color-block-background-light);
|
||||
border-radius: 3px;
|
||||
margin-bottom: 8px;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.lc-listitem {
|
||||
position: relative;
|
||||
outline: none;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
height: 32px;
|
||||
height: 34px;
|
||||
|
||||
.lc-listitem-actions {
|
||||
margin: 0 3px;
|
||||
@ -54,9 +57,20 @@
|
||||
.lc-listitem-body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: stretch;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
text-overflow: ellipsis;
|
||||
.lc-field {
|
||||
padding: 0 !important;
|
||||
}
|
||||
> * {
|
||||
width: 100%;
|
||||
}
|
||||
.next-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.lc-listitem-handler {
|
||||
margin-left: 2px;
|
||||
|
||||
@ -0,0 +1,167 @@
|
||||
import { Component, Fragment } from 'react';
|
||||
import { Icon, Button } from '@alifd/next';
|
||||
import { FieldConfig, SettingField, SetterType } from '../../main';
|
||||
import { createSettingFieldView } from '../../settings-pane';
|
||||
import { PopupContext, PopupPipe } from '../../popup';
|
||||
import Title from '../../title';
|
||||
import './style.less';
|
||||
|
||||
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 ? false : true} />;
|
||||
} else {
|
||||
return <RowSetter columns={forceInline > 1 ? 2 : 4} {...props} />;
|
||||
}
|
||||
} else {
|
||||
// form
|
||||
return <FormSetter />;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ObjectSetterConfig {
|
||||
items?: FieldConfig[];
|
||||
extraConfig?: {
|
||||
setter?: SetterType;
|
||||
defaultValue?: any | ((field: SettingField, editor: any) => any);
|
||||
};
|
||||
}
|
||||
|
||||
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.required || conf.important) {
|
||||
const item = field.createField({
|
||||
...conf,
|
||||
// in column-cell
|
||||
forceInline: 3,
|
||||
});
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (items.length > 0) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
if (descriptor) {
|
||||
if (typeof descriptor === 'function') {
|
||||
let firstRun: boolean = true;
|
||||
field.onEffect(() => {
|
||||
const state = {
|
||||
descriptor: descriptor(field),
|
||||
};
|
||||
if (firstRun) {
|
||||
firstRun = false;
|
||||
this.state = state;
|
||||
} else {
|
||||
this.setState(state);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.state = {
|
||||
descriptor,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// todo: onEffect change field.name
|
||||
this.state = {
|
||||
descriptor: field.title || `项目 ${field.name}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate(_: any, nextState: any) {
|
||||
if (this.state.decriptor !== nextState.decriptor) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private pipe: any;
|
||||
render() {
|
||||
const items = this.items;
|
||||
const { field, primaryButton } = 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} />, 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);
|
||||
}}
|
||||
>
|
||||
<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);
|
||||
}}
|
||||
>
|
||||
<Icon type="edit" />
|
||||
{title}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// form-field setter
|
||||
class FormSetter extends Component<{}> {
|
||||
render() {
|
||||
return 'yes';
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
import { Component } from "react";
|
||||
import { FieldConfig, SettingField } 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<{
|
||||
decriptor?: string | ((rowField: SettingField) => string);
|
||||
config: ObjectSetterConfig;
|
||||
columnsLimit?: number;
|
||||
}> {
|
||||
render() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// form-field setter
|
||||
class FormSetter extends Component<{}> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -37,6 +37,7 @@
|
||||
}
|
||||
> .lc-field-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import Title from './title';
|
||||
import SettingsPane, { registerSetter, createSetterContent, getSetter, createSettingFieldView } from './settings-pane';
|
||||
import Node from '../../designer/src/designer/document/node/node';
|
||||
import ArraySetter from './builtin-setters/array-setter';
|
||||
import ObjectSetter from './builtin-setters/object-setter';
|
||||
|
||||
export default class SettingsMainView extends Component {
|
||||
private main: SettingsMain;
|
||||
@ -124,5 +125,6 @@ function selectNode(node: Node) {
|
||||
}
|
||||
|
||||
registerSetter('ArraySetter', ArraySetter);
|
||||
registerSetter('ObjectSetter', ObjectSetter);
|
||||
|
||||
export { registerSetter, createSetterContent, getSetter, createSettingFieldView };
|
||||
|
||||
142
packages/plugin-settings/src/popup/index.tsx
Normal file
142
packages/plugin-settings/src/popup/index.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
import { createContext, ReactNode, Component, PureComponent } from 'react';
|
||||
import { EventEmitter } from 'events';
|
||||
import { Balloon } from '@alifd/next';
|
||||
import { uniqueId } from '../../../utils/unique-id';
|
||||
import './style.less';
|
||||
|
||||
export const PopupContext = createContext<PopupPipe>({} as any);
|
||||
|
||||
export class PopupPipe {
|
||||
private emitter = new EventEmitter();
|
||||
private currentId?: string;
|
||||
|
||||
create(props?: object): { send: (content: ReactNode, title: ReactNode) => void; show: (target: Element) => void } {
|
||||
let sendContent: ReactNode = null;
|
||||
let sendTitle: ReactNode = null;
|
||||
const id = uniqueId('popup');
|
||||
return {
|
||||
send: (content: ReactNode, title: ReactNode) => {
|
||||
sendContent = content;
|
||||
sendTitle = title;
|
||||
if (this.currentId === id) {
|
||||
this.popup({
|
||||
...props,
|
||||
content,
|
||||
title,
|
||||
});
|
||||
}
|
||||
},
|
||||
show: (target: Element) => {
|
||||
this.currentId = id;
|
||||
this.popup({
|
||||
...props,
|
||||
content: sendContent,
|
||||
title: sendTitle,
|
||||
}, target);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private popup(props: object, target?: Element) {
|
||||
Promise.resolve().then(() => {
|
||||
this.emitter.emit('popupchange', props, target);
|
||||
});
|
||||
}
|
||||
|
||||
onPopupChange(fn: (props: object, target?: Element) => void): () => void {
|
||||
this.emitter.on('popupchange', fn);
|
||||
return () => {
|
||||
this.emitter.removeListener('popupchange', fn);
|
||||
};
|
||||
}
|
||||
|
||||
purge() {
|
||||
this.emitter.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
export default class PopupService extends Component<{ safeId?: string }> {
|
||||
private popupPipe = new PopupPipe();
|
||||
|
||||
componentWillUnmount() {
|
||||
this.popupPipe.purge();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<PopupContext.Provider value={this.popupPipe}>
|
||||
{this.props.children}
|
||||
<PopupContent safeId={this.props.safeId} />
|
||||
</PopupContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class PopupContent extends PureComponent<{ safeId?: string }> {
|
||||
static contextType = PopupContext;
|
||||
state: any = {
|
||||
visible: false,
|
||||
pos: {},
|
||||
};
|
||||
|
||||
private dispose = (this.context as PopupPipe).onPopupChange((props, target) => {
|
||||
const state: any = {
|
||||
...props,
|
||||
visible: true,
|
||||
};
|
||||
if (target) {
|
||||
const rect = target.getBoundingClientRect();
|
||||
state.pos = {
|
||||
top: rect.top,
|
||||
height: rect.height,
|
||||
};
|
||||
// todo: compute the align method
|
||||
}
|
||||
this.setState(state);
|
||||
});
|
||||
|
||||
componentWillUnmount() {
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { content, visible, width, title, pos } = this.state;
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
let avoidLaterHidden = true;
|
||||
setTimeout(() => {
|
||||
avoidLaterHidden = false;
|
||||
}, 10);
|
||||
|
||||
const id = uniqueId('ball');
|
||||
|
||||
return (
|
||||
<Balloon
|
||||
className="lc-ballon"
|
||||
align="l"
|
||||
id={this.props.safeId}
|
||||
safeId={this.props.safeId}
|
||||
safeNode={id}
|
||||
visible={visible}
|
||||
style={{ width }}
|
||||
onVisibleChange={visible => {
|
||||
if (avoidLaterHidden) {
|
||||
return;
|
||||
}
|
||||
if (!visible) {
|
||||
this.setState({ visible: false });
|
||||
}
|
||||
}}
|
||||
trigger={<div className="lc-popup-placeholder" style={pos} />}
|
||||
triggerType="click"
|
||||
animation={false}
|
||||
// needAdjust
|
||||
shouldUpdatePosition
|
||||
>
|
||||
<div className="lc-ballon-title">{title}</div>
|
||||
<div className="lc-ballon-content"><PopupService safeId={id}>{content}</PopupService></div>
|
||||
</Balloon>
|
||||
);
|
||||
}
|
||||
}
|
||||
22
packages/plugin-settings/src/popup/style.less
Normal file
22
packages/plugin-settings/src/popup/style.less
Normal file
@ -0,0 +1,22 @@
|
||||
.lc-popup-placeholder {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.lc-ballon {
|
||||
padding: 10px;
|
||||
max-width: 640px;
|
||||
width: 640px;
|
||||
.lc-ballon-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
.lc-ballon-content {
|
||||
margin-top: 10px;
|
||||
// width: 300px;
|
||||
}
|
||||
.next-balloon-close {
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,8 @@ import {
|
||||
} from './main';
|
||||
import { Field, FieldGroup } from './field';
|
||||
import { TitleContent } from './title';
|
||||
import { Balloon } from '@alifd/next';
|
||||
import PopupService from './popup';
|
||||
|
||||
export type RegisteredSetter = {
|
||||
component: CustomView;
|
||||
@ -229,10 +231,6 @@ export function createSettingFieldView(item: SettingField | CustomView, field: S
|
||||
}
|
||||
}
|
||||
|
||||
export function showPopup() {
|
||||
|
||||
}
|
||||
|
||||
export default class SettingsPane extends Component<{ target: SettingTarget }> {
|
||||
state: { items: Array<SettingField | CustomView> } = {
|
||||
items: [],
|
||||
@ -272,7 +270,12 @@ export default class SettingsPane extends Component<{ target: SettingTarget }> {
|
||||
const { target } = this.props;
|
||||
return (
|
||||
<div className="lc-settings-pane">
|
||||
{items.map((item, index) => createSettingFieldView(item, target, index))}
|
||||
{/* todo: add head for single use */}
|
||||
<PopupService>
|
||||
<div className="lc-settings-content">
|
||||
{items.map((item, index) => createSettingFieldView(item, target, index))}
|
||||
</div>
|
||||
</PopupService>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user