fix: settings pane

This commit is contained in:
kangwei 2020-04-21 04:31:43 +08:00
parent 92ea6505c1
commit 27db010b8a
17 changed files with 244 additions and 151 deletions

View File

@ -38,6 +38,10 @@ export interface FieldExtraProps {
* internal use * internal use
*/ */
forceInline?: number; forceInline?: number;
/**
* compatiable vision display
*/
display?: 'accordion' | 'inline' | 'block' | 'plain' | 'popup' | 'entry';
} }
export interface FieldConfig extends FieldExtraProps { export interface FieldConfig extends FieldExtraProps {

View File

@ -7,44 +7,89 @@ import './index.less';
export interface FieldProps { export interface FieldProps {
className?: string; className?: string;
// span
title?: TitleContent | null; title?: TitleContent | null;
defaultDisplay?: 'accordion' | 'inline' | 'block';
collapsed?: boolean;
onExpandChange?: (expandState: boolean) => void;
} }
export class CommonField extends Component<FieldProps> { export class Field extends Component<FieldProps> {
private shell: HTMLDivElement | null = null; state = {
collapsed: this.props.collapsed,
display: this.props.defaultDisplay || 'inline',
};
private checkIsBlockField() { private toggleExpand = () => {
if (this.shell) { const { onExpandChange } = this.props;
const setter = this.shell.lastElementChild!.firstElementChild; const collapsed = !this.state.collapsed;
if (setter && setter.classList.contains('lc-block-setter')) { this.setState({
this.shell.classList.add('lc-block-field'); collapsed,
this.shell.classList.remove('lc-inline-field'); });
} else { onExpandChange && onExpandChange(!collapsed);
this.shell.classList.remove('lc-block-field'); };
this.shell.classList.add('lc-inline-field'); private body: HTMLDivElement | null = null;
} private dispose?: () => void;
private deployBlockTesting() {
if (this.dispose) {
this.dispose();
} }
} const body = this.body;
componentDidUpdate() { if (!body) {
this.checkIsBlockField(); return;
}
const check = () => {
const setter = body.firstElementChild;
if (setter && setter.classList.contains('lc-block-setter')) {
this.setState({
display: 'block',
});
} else {
this.setState({
display: 'inline',
});
}
};
const observer = new MutationObserver(check);
check();
observer.observe(body, {
childList: true,
subtree: false,
attributes: true,
attributeFilter: ['class'],
});
this.dispose = () => observer.disconnect();
} }
componentDidMount() { componentDidMount() {
this.checkIsBlockField(); const { defaultDisplay } = this.props;
if (!defaultDisplay || defaultDisplay === 'inline') {
this.deployBlockTesting();
}
}
componentWillUnmount() {
if (this.dispose) {
this.dispose();
}
} }
render() { render() {
const { className, children, title } = this.props; const { className, children, title } = this.props;
const { display, collapsed } = this.state;
const isAccordion = display === 'accordion';
return ( return (
<div ref={(shell) => (this.shell = shell)} className={classNames('lc-field lc-inline-field', className)}> <div
{title && ( className={classNames(`lc-field lc-${display}-field`, className, {
<div className="lc-field-head"> 'lc-field-is-collapsed': isAccordion && collapsed,
<div className="lc-field-title"> })}
<Title title={title} /> >
</div> <div className="lc-field-head" onClick={isAccordion ? this.toggleExpand : undefined}>
<div className="lc-field-title">
<Title title={title || ''} />
</div> </div>
)} {isAccordion && <Icon className="lc-field-icon" type="arrow-up" size="xs" />}
<div className="lc-field-body">{children}</div> </div>
<div key="body" ref={(shell) => (this.body = shell)} className="lc-field-body">
{children}
</div>
</div> </div>
); );
} }
@ -96,15 +141,13 @@ export class PopupField extends Component<PopupFieldProps> {
} }
} }
export type EntryFieldProps = FieldProps; export interface EntryFieldProps extends FieldProps {
stageName?: string;
}
export class EntryField extends Component<EntryFieldProps> { export class EntryField extends Component<EntryFieldProps> {
constructor(props: any) {
super(props);
}
render() { render() {
const { propName, stageName, tip, title, className } = this.props; const { stageName, title, className } = this.props;
const classNameList = classNames('engine-setting-field', 'engine-entry-field', className); const classNameList = classNames('engine-setting-field', 'engine-entry-field', className);
const fieldProps: any = {}; const fieldProps: any = {};
@ -117,8 +160,8 @@ export class EntryField extends Component<EntryFieldProps> {
<span className="engine-field-title" key="field-title"> <span className="engine-field-title" key="field-title">
{title} {title}
</span>, </span>,
renderTip(tip, { propName }), // renderTip(tip, { propName }),
<Icons name="arrow" className="engine-field-arrow" size="12px" key="engine-field-arrow-icon" />, // <Icons name="arrow" className="engine-field-arrow" size="12px" key="engine-field-arrow-icon" />,
]; ];
return ( return (
@ -128,3 +171,14 @@ export class EntryField extends Component<EntryFieldProps> {
); );
} }
} }
export class PlainField extends Component<FieldProps> {
render() {
const { className, children } = this.props;
return (
<div className={classNames(`lc-field lc-plain-field`, className)}>
<div className="lc-field-body">{children}</div>
</div>
);
}
}

View File

@ -21,6 +21,17 @@
} }
} }
&.lc-plain-field {
// for top-level style
padding: 8px 10px;
> .lc-field-body {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
}
}
&.lc-inline-field { &.lc-inline-field {
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -0,0 +1,28 @@
import { ReactNode, createElement } from 'react';
import { TitleContent } from '@ali/lowcode-globals';
import './index.less';
import { Field, PopupField, EntryField, PlainField } from './fields';
export interface FieldProps {
className?: string;
title?: TitleContent | null;
display?: 'accordion' | 'inline' | 'block' | 'plain' | 'popup' | 'entry';
collapsed?: boolean;
onExpandChange?: (collapsed: boolean) => void;
[extra: string]: any;
}
export function createField(props: FieldProps, children: ReactNode, type?: 'accordion' | 'inline' | 'block' | 'plain' | 'popup' | 'entry') {
if (type === 'popup') {
return createElement(PopupField, props, children);
}
if (type === 'entry') {
return createElement(EntryField, props, children);
}
if (type === 'plain' || !props.title) {
return createElement(PlainField, props, children);
}
return createElement(Field, { ...props, defaultDisplay: type }, children);
}
export { Field, PopupField, EntryField, PlainField };

View File

@ -1,65 +0,0 @@
import { Component } from 'react';
import classNames from 'classnames';
import { Icon } from '@alifd/next';
import { Title, TitleContent } from '@ali/lowcode-globals';
import './index.less';
import { CommonField, PopupField } from './fields';
export interface FieldProps {
className?: string;
// span
title?: TitleContent | null;
type?: string;
}
export class Field extends Component<FieldProps> {
render() {
const { type, ...rest } = this.props;
if (type === 'popup') {
return <PopupField {...rest} />;
}
return <CommonField {...rest} />;
}
}
export interface FieldGroupProps extends FieldProps {
defaultCollapsed?: boolean;
onExpandChange?: (collapsed: boolean) => void;
}
export class FieldGroup extends Component<FieldGroupProps> {
state = {
collapsed: this.props.defaultCollapsed,
};
toggleExpand() {
const { onExpandChange } = this.props;
const collapsed = !this.state.collapsed;
this.setState({
collapsed,
});
onExpandChange && onExpandChange(collapsed);
}
render() {
const { className, children, title } = this.props;
return (
<div
className={classNames('lc-field lc-accordion-field', className, {
'lc-field-is-collapsed': this.state.collapsed,
})}
>
{title && (
<div className="lc-field-head" onClick={this.toggleExpand.bind(this)}>
<div className="lc-field-title">
<Title title={title} />
</div>
<Icon className="lc-field-icon" type="arrow-up" size="xs" />
</div>
)}
<div className="lc-field-body">{children}</div>
</div>
);
}
}

View File

@ -0,0 +1,5 @@
* 拖拽排序有问题
* forceInline 有问题
* 部分改变不响应
* 样式还原
* autofocus

View File

@ -133,7 +133,7 @@ export class ListSetter extends Component<ArraySetterProps, ArraySetterState> {
render() { render() {
let columns: any = null; let columns: any = null;
if (this.props.columns) { if (this.props.columns) {
columns = this.props.columns.map((column) => <Title title={column.title || (column.name as string)} />); columns = this.props.columns.map((column) => <Title key={column.name} title={column.title || (column.name as string)} />);
} }
const { items } = this.state; const { items } = this.state;

View File

@ -75,6 +75,11 @@
text-overflow: ellipsis; text-overflow: ellipsis;
.lc-field { .lc-field {
padding: 0 !important; padding: 0 !important;
display: flex;
align-items: center;
>.lc-field-body {
justify-content: center;
}
} }
> * { > * {
width: 100%; width: 100%;

View File

@ -103,22 +103,6 @@ export default class MixedSetter extends Component<{
}); });
} }
private checkIsBlockField() {
if (this.shell) {
const setter = this.shell.lastElementChild!.firstElementChild;
if (setter && setter.classList.contains('lc-block-setter')) {
this.shell.classList.add('lc-block-setter');
} else {
this.shell.classList.remove('lc-block-field');
}
}
}
componentDidUpdate() {
this.checkIsBlockField();
}
componentDidMount() {
this.checkIsBlockField();
}
private useSetter: (id: string) => { private useSetter: (id: string) => {
const { field, onChange } = this.props; const { field, onChange } = this.props;

View File

@ -391,4 +391,47 @@ export class SettingPropEntry implements SettingTarget {
onValueChange() { onValueChange() {
return () => {}; return () => {};
} }
getId() {
return this.id;
}
getName(): string {
return this.path.join('.');
}
getKey() {
return this.name;
}
/*
getDefaultValue() {
return this.extraProps.defaultValue;
}
getConfig<K extends keyof IPropConfig>(configName?: K): IPropConfig[K] | IPropConfig {
if (configName) {
return this.config[configName];
}
return this.config;
}
*/
/*
isHidden() {
return false;
}
isDisabled() {
return false;
}
getSetter() {
}
*/
isIgnore() {
return false;
}
} }

View File

@ -1,4 +1,4 @@
import { TitleContent, computed, isDynamicSetter, SetterType, DynamicSetter, FieldExtraProps, FieldConfig, CustomView, isCustomView } from '@ali/lowcode-globals'; import { TitleContent, computed, isDynamicSetter, SetterType, DynamicSetter, FieldExtraProps, FieldConfig, CustomView, isCustomView, obx } from '@ali/lowcode-globals';
import { Transducer } from '../utils'; import { Transducer } from '../utils';
import { SettingPropEntry } from './setting-entry'; import { SettingPropEntry } from './setting-entry';
import { SettingTarget } from './setting-target'; import { SettingTarget } from './setting-target';
@ -26,9 +26,20 @@ export class SettingField extends SettingPropEntry implements SettingTarget {
return this._setter; return this._setter;
} }
@obx.ref private _expanded = true;
get expanded(): boolean {
return this._expanded;
}
setExpanded(value: boolean) {
this._expanded = value;
}
constructor(readonly parent: SettingTarget, config: FieldConfig) { constructor(readonly parent: SettingTarget, config: FieldConfig) {
super(parent, config.name, config.type); super(parent, config.name, config.type);
console.info(config);
const { title, items, setter, extraProps, ...rest } = config; const { title, items, setter, extraProps, ...rest } = config;
this._title = title; this._title = title;
this._setter = setter; this._setter = setter;
@ -37,6 +48,7 @@ export class SettingField extends SettingPropEntry implements SettingTarget {
...extraProps, ...extraProps,
}; };
this.isRequired = config.isRequired || (setter as any)?.isRequired; this.isRequired = config.isRequired || (setter as any)?.isRequired;
this._expanded = extraProps?.defaultCollapsed ? false : true;
// initial items // initial items
if (this.type === 'group' && items) { if (this.type === 'group' && items) {

View File

@ -8,7 +8,7 @@ import {
createSetterContent, createSetterContent,
observer, observer,
} from '@ali/lowcode-globals'; } from '@ali/lowcode-globals';
import { Field, FieldGroup } from '../field'; import { Field, createField } from '../field';
import PopupService from '../popup'; import PopupService from '../popup';
import { SettingField, isSettingField } from './setting-field'; import { SettingField, isSettingField } from './setting-field';
import { SettingTarget } from './setting-target'; import { SettingTarget } from './setting-target';
@ -20,7 +20,7 @@ class SettingFieldView extends Component<{ field: SettingField }> {
const { field } = this.props; const { field } = this.props;
const { extraProps } = field; const { extraProps } = field;
const { condition, defaultValue } = extraProps; const { condition, defaultValue } = extraProps;
const visible = field.isOneNode && typeof condition === 'function' ? !condition(field) : true; const visible = field.isOneNode && typeof condition === 'function' ? condition(field) !== false : true;
if (!visible) { if (!visible) {
return null; return null;
} }
@ -62,28 +62,29 @@ class SettingFieldView extends Component<{ field: SettingField }> {
} }
} }
} }
// todo: error handling // todo: error handling
return ( return createField({
<Field title={extraProps.forceInline ? null : field.title}> title: field.title,
{createSetterContent(setterType, { collapsed: !field.expanded,
...shallowIntl(setterProps), onExpandChange: (expandState) => field.setExpanded(expandState),
forceInline: extraProps.forceInline, }, createSetterContent(setterType, {
key: field.id, ...shallowIntl(setterProps),
// === injection forceInline: extraProps.forceInline,
prop: field, // for compatible vision key: field.id,
field, // === injection
// === IO prop: field, // for compatible vision
value, // reaction point field,
onChange: (value: any) => { // === IO
this.setState({ value, // reaction point
value, onChange: (value: any) => {
}); this.setState({
field.setValue(value); value,
}, });
})} field.setValue(value);
</Field> },
); }), extraProps.forceInline ? 'plain' : extraProps.display);
} }
} }
@ -96,17 +97,20 @@ class SettingGroupView extends Component<{ field: SettingField }> {
render() { render() {
const { field } = this.props; const { field } = this.props;
const { extraProps } = field; const { extraProps } = field;
const { condition, defaultCollapsed } = extraProps; const { condition } = extraProps;
const visible = field.isOneNode && typeof condition === 'function' ? !condition(field) : true; const visible = field.isOneNode && typeof condition === 'function' ? condition(field) !== false : true;
if (!visible) { if (!visible) {
return null; return null;
} }
// todo: split collapsed state | field.items for optimize
return ( return (
<FieldGroup title={field.title} defaultCollapsed={defaultCollapsed}> <Field defaultDisplay="accordion" title={field.title} collapsed={!field.expanded} onExpandChange={(expandState) => {
field.setExpanded(expandState);
}}>
{field.items.map((item, index) => createSettingFieldView(item, field, index))} {field.items.map((item, index) => createSettingFieldView(item, field, index))}
</FieldGroup> </Field>
); );
} }
} }

View File

@ -54,10 +54,6 @@
overflow-y: auto; overflow-y: auto;
} }
.lc-settings-pane {
padding-bottom: 50px;
}
// ====== reset fusion-tabs ===== // ====== reset fusion-tabs =====
.lc-settings-tabs { .lc-settings-tabs {
position: relative; position: relative;
@ -114,6 +110,13 @@
} }
} }
.lc-settings-pane {
padding-bottom: 50px;
.next-btn {
line-height: 1 !important;
}
}
html.lc-cursor-dragging:not(.lowcode-has-fixed-tree) { html.lc-cursor-dragging:not(.lowcode-has-fixed-tree) {
.lc-settings-main .lc-outline-pane { .lc-settings-main .lc-outline-pane {
display: block; display: block;

View File

@ -102,11 +102,12 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
title: 'Ref', title: 'Ref',
setter: 'StringSetter', setter: 'StringSetter',
}, },
/*
{ {
name: '!more', name: '!more',
title: '更多', title: '更多',
setter: 'PropertiesSetter', setter: 'PropertiesSetter',
}, },*/
], ],
}); });
const combined: FieldConfig[] = [ const combined: FieldConfig[] = [
@ -168,11 +169,13 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp
} }
if (isRoot) { if (isRoot) {
/*
combined.push({ combined.push({
name: '#advanced', name: '#advanced',
title: { type: 'i18n', 'zh-CN': '高级', 'en-US': 'Advance' }, title: { type: 'i18n', 'zh-CN': '高级', 'en-US': 'Advance' },
items: [], items: [],
}); });
*/
} else { } else {
combined.push({ combined.push({
name: '#advanced', name: '#advanced',

View File

@ -216,7 +216,7 @@ export function upgradePropConfig(config: OldPropConfig) {
if (tip) { if (tip) {
if (typeof title !== 'object' || isI18nData(title) || isValidElement(title)) { if (typeof title !== 'object' || isI18nData(title) || isValidElement(title)) {
newConfig.title = { newConfig.title = {
title, label: title,
tip: tip.content, tip: tip.content,
docUrl: tip.url docUrl: tip.url
}; };
@ -615,7 +615,9 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
experimental.callbacks = callbacks; experimental.callbacks = callbacks;
const props = upgradeConfigure(configure || []); const props = upgradeConfigure(configure || []);
meta.configure = { props, component }; const events = {};
const styles = {};
meta.configure = { props, component, events, styles };
meta.experimental = experimental; meta.experimental = experimental;
return meta; return meta;
} }

View File

@ -16,7 +16,7 @@ async function load() {
const externals = ['react', 'react-dom', 'prop-types', 'react-router', 'react-router-dom', '@ali/recore']; const externals = ['react', 'react-dom', 'prop-types', 'react-router', 'react-router-dom', '@ali/recore'];
async function loadAssets() { async function loadAssets() {
const assets = await editor.utils.get('./assets.json'); const assets = await editor.utils.get('./legao-assets.json');
if (assets.packages) { if (assets.packages) {
assets.packages.forEach((item: any) => { assets.packages.forEach((item: any) => {

View File

@ -11,7 +11,7 @@ html.engine-cursor-ew-resize, html.engine-cursor-ew-resize * {
} }
body, #engine { body, #engine {
position: absolute; position: fixed;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;