joint editor & designer

This commit is contained in:
kangwei 2020-03-10 22:35:41 +08:00
parent fa1062ace9
commit 901e06ac01
42 changed files with 269 additions and 202 deletions

View File

@ -45,7 +45,7 @@
}
}
&-device-legao {
&-device-default {
top: 15px;
right: 15px;
bottom: 15px;

View File

@ -81,7 +81,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
@computed get device(): string | undefined {
// 根据 device 不同来做画布外框样式变化 渲染时可选择不同组件
// renderer 依赖
return this.get('device');
return this.get('device') || 'default';
}
@computed get deviceClassName(): string | undefined {

View File

@ -86,7 +86,7 @@ function npmToURI(npm: {
main?: string;
version: string;
}): string {
let pkg = [];
const pkg = [];
if (npm.package) {
pkg.push(npm.package);
}
@ -143,63 +143,91 @@ export class ComponentType {
}
private _configure?: Configure;
get configure() {
return [{
name: '#props',
title: "属性",
items: [{
name: 'label',
title: '标签',
setter: 'StringSetter'
}, {
name: 'name',
title: '名称',
setter: {
componentName: 'ArraySetter',
props: {
itemConfig: {
setter: 'StringSetter',
defaultValue: ''
}
}
}
}, {
name: 'size',
title: '大小',
setter: 'StringSetter'
}, {
name: 'age',
title: '年龄',
setter: 'NumberSetter'
}]
}, {
name: '#styles',
title: "样式",
items: [{
name: 'className',
title: '类名绑定',
setter: 'ClassNameSetter'
}, {
name: 'className2',
title: '类名绑定',
setter: 'StringSetter'
}, {
name: '#inlineStyles',
title: '行内样式',
items: []
}]
}, {
name: '#events',
title: "事件",
items: [{
name: '!events',
title: '事件绑定',
setter: 'EventsSetter'
}]
}, {
name: '#data',
title: "数据",
items: []
}];
return [
{
name: '#props',
title: '属性',
items: [
{
name: 'label',
title: '标签',
setter: 'StringSetter',
},
{
name: 'name',
title: '名称',
setter: {
componentName: 'ArraySetter',
props: {
itemConfig: {
setter: 'StringSetter',
defaultValue: '',
},
},
},
},
{
name: 'size',
title: '大小',
setter: 'StringSetter',
},
{
name: 'age',
title: '年龄',
setter: 'NumberSetter',
},
],
},
{
name: '#styles',
title: '样式',
items: [
{
name: 'className',
title: '类名绑定',
setter: 'ClassNameSetter',
},
{
name: 'className2',
title: '类名绑定',
setter: 'StringSetter',
},
{
name: '#inlineStyles',
title: '行内样式',
items: [],
},
],
},
{
name: '#events',
title: '事件',
items: [
{
name: '!events',
title: '事件绑定',
setter: {
componentName: 'EventsSetter',
},
extraProps: {
getValue(field: any) {
console.info('lifeCycles', field.getExtraPropValue('lifeCycles'));
return field.getPropValue('xxx');
},
setValue(field: any, val: any) {
field.setExtraPropValue('lifeCycles', val);
field.setPropValue('xxx', val);
},
},
},
],
},
{
name: '#data',
title: '数据',
items: [],
},
];
}
private parentWhitelist?: string[] | null;

View File

@ -17,9 +17,9 @@ import Props from './props/props';
* meta
* state
* defaultProps
* dataSource
* lifeCycles
* methods
* dataSource
* css
*
* [Directives **not used**]
@ -42,16 +42,16 @@ export default class RootNode extends Node implements NodeParent {
return 0;
}
get nextSibling() {
return null
return null;
}
get prevSibling() {
return null
return null;
}
get zLevel() {
return 0;
}
get parent() {
return null
return null;
}
get children(): NodeChildren {
return this._children as NodeChildren;
@ -59,7 +59,9 @@ export default class RootNode extends Node implements NodeParent {
get props(): Props {
return this._props as any;
}
internalSetParent(parent: null) {}
internalSetParent(parent: null) {
// empty
}
constructor(readonly document: DocumentModel, rootSchema: RootSchema) {
super(document, rootSchema);

View File

@ -1,6 +1,7 @@
import logo from '../plugins/logo';
import designer from '../plugins/designer';
import Designer from '../plugins/designer';
import undoRedo from '../plugins/undoRedo';
import Settings from '../../../plugin-settings';
import topBalloonIcon from '@ali/iceluna-addon-2';
import topDialogIcon from '@ali/iceluna-addon-2';
import leftPanelIcon from '@ali/iceluna-addon-2';
@ -16,7 +17,8 @@ import PluginFactory from '../framework/pluginFactory';
export default {
logo: PluginFactory(logo),
designer: PluginFactory(designer),
designer: PluginFactory(Designer),
settings: PluginFactory(Settings),
undoRedo: PluginFactory(undoRedo),
topBalloonIcon: PluginFactory(topBalloonIcon),
topDialogIcon: PluginFactory(topDialogIcon),

View File

@ -0,0 +1,16 @@
import { Input } from '@alifd/next';
import NumberSetter from '../../../plugin-setters/number-setter';
import { registerSetter } from '../../../plugin-settings/src';
import { createElement } from 'react';
registerSetter('ClassNameSetter', () => {
return createElement('div', {
className: 'lc-block-setter'
}, '这里是类名绑定');
});
registerSetter('EventsSetter', Input);
registerSetter('StringSetter', { component: Input, props: { placeholder: "请输入" } });
registerSetter('NumberSetter', NumberSetter as any);

View File

@ -204,54 +204,14 @@ export default {
],
rightArea: [
{
pluginKey: 'rightPanel1',
pluginKey: 'settings',
type: 'Panel',
props: {
title: '样式'
},
props: {},
config: {
package: '@ali/iceluna-addon-2',
version: '^1.0.0'
},
pluginProps: {}
},
{
pluginKey: 'rightPanel2',
type: 'Panel',
props: {
title: '属性',
icon: 'dengpao'
},
config: {
package: '@ali/iceluna-addon-2',
version: '^1.0.0'
},
pluginProps: {}
},
{
pluginKey: 'rightPanel3',
type: 'Panel',
props: {
title: '事件'
},
config: {
package: '@ali/iceluna-addon-2',
version: '^1.0.0'
},
pluginProps: {}
},
{
pluginKey: 'rightPanel4',
type: 'Panel',
props: {
title: '数据'
},
config: {
package: '@ali/iceluna-addon-2',
version: '^1.0.0'
},
pluginProps: {}
}
],
centerArea: [{
pluginKey: 'designer',

View File

@ -112,7 +112,10 @@ export interface Utils {
[propName: string]: (...args) => any;
}
export interface PluginClass extends React.Component {
export interface PluginClass extends React.ComponentClass<{
editor: Editor;
[key: string]: any
}> {
init?: (editor: Editor) => void;
open?: () => any;
close?: () => any;

View File

@ -7,13 +7,14 @@ import config from './config/skeleton';
import components from './config/components';
import utils from './config/utils';
import constants from './config/constants';
import messages from './config/locale';
import './config/locale';
import './config/setters';
import pkg from '../package.json';
import './global.scss';
import './config/theme.scss';
window.__pkg = pkg;
(window as any).__pkg = pkg;
const ICE_CONTAINER = document.getElementById('ice-container');

View File

@ -1,5 +1,4 @@
.lowcode-plugin-designer {
background-color: #ffffff;
width: 100%;
height: 100%;
}
}

View File

@ -172,29 +172,27 @@ export default class DesignerPlugin extends PureComponent<PluginProps> {
render() {
const { editor } = this.props;
return (
<div className="lowcode-plugin-designer">
<Designer
defaultSchema={SCHEMA as any}
eventPipe={editor as any}
simulatorProps={{
componentsAsset: [
{
type: 'jsUrl',
content: 'https://unpkg.alibaba-inc.com/@alifd/next@1.18.17/dist/next.min.js',
id: 'next',
level: 2
},
{
type: 'cssUrl',
content: 'https://unpkg.alibaba-inc.com/@alifd/next@1.18.17/dist/next.min.css',
id: 'next',
level: 2
}
],
// simulatorUrl: ['/statics/simulator-renderer.css', '/statics/simulator-renderer.js']
}}
/>
</div>
<Designer
className="lowcode-plugin-designer"
defaultSchema={SCHEMA as any}
eventPipe={editor as any}
simulatorProps={{
componentsAsset: [
{
type: 'jsUrl',
content: 'https://unpkg.alibaba-inc.com/@alifd/next@1.18.17/dist/next.min.js',
id: 'next',
level: 2
},
{
type: 'cssUrl',
content: 'https://unpkg.alibaba-inc.com/@alifd/next@1.18.17/dist/next.min.css',
id: 'next',
level: 2
}
]
}}
/>
);
}
}

View File

@ -20,13 +20,12 @@ body {
right: 0;
bottom: 0;
display: flex;
background-color: #d8d8d8;
background-color: rgba(31, 56, 88, 0.06);
}
.lowcode-center-area {
flex: 1;
display: flex;
flex-direction: column;
padding: 10px;
overflow: auto;
}
}

View File

@ -1,3 +1,3 @@
.lowcode-center-area {
padding: 12px;
padding: 0;
}

View File

@ -128,6 +128,18 @@ export default class RightArea extends PureComponent<RightAreaProps, RightAreaSt
render() {
const visiblePluginList = this.areaManager.getVisiblePluginList();
if (visiblePluginList.length < 2) {
const pane = visiblePluginList[0];
if (!pane) {
return <div className="lowcode-right-area"></div>;
}
const Comp = this.editor.components[pane.pluginKey];
return (
<div className="lowcode-right-area">
<Comp editor={this.editor} config={pane} {...pane.pluginProps} />
</div>
);
}
return (
<div className="lowcode-right-area">
<Tab

View File

@ -66,7 +66,6 @@ export default class Skeleton extends PureComponent<SkeletonProps, SkeletonState
this.editor.destroy();
}
const { utils, config, components } = this.props;
debugger;
const editor = (this.editor = new Editor(comboEditorConfig(defaultConfig, config), components, {
...skeletonUtils,
...utils

View File

@ -1,5 +1,5 @@
{
"name": "@ali/lowcode-plugin-settings-pane",
"name": "@ali/lowcode-plugin-settings",
"version": "0.0.0",
"description": "xxx for Ali lowCode engine",
"main": "src/index.tsx",

View File

@ -10,20 +10,20 @@ interface ArraySetterState {
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) {
interface ArraySetterProps {
value: any[];
field: SettingField;
itemConfig?: {
setter?: SetterType;
defaultValue?: any | ((field: SettingField) => any);
required?: boolean;
};
multiValue?: boolean;
}
export class ListSetter extends Component<ArraySetterProps, ArraySetterState> {
static getDerivedStateFromProps(props: ArraySetterProps, state: ArraySetterState) {
const { value, field } = props;
const newLength = value && Array.isArray(value) ? value.length : 0;
if (state && state.prevLength === newLength) {
@ -63,6 +63,12 @@ export class ListSetter extends Component<
};
}
state: ArraySetterState = {
items: [],
itemsMap: new Map<string | number, SettingField>(),
prevLength: 0,
};
onSort(sortedIds: Array<string | number>) {
const { itemsMap } = this.state;
const items = sortedIds.map((id, index) => {
@ -87,7 +93,7 @@ export class ListSetter extends Component<
});
items.push(item);
itemsMap.set(item.id, item);
item.setValue(typeof defaultValue === 'function' ? defaultValue(item, item.editor) : defaultValue);
item.setValue(typeof defaultValue === 'function' ? defaultValue(item) : defaultValue);
this.scrollToLast = true;
this.setState({
items: items.slice(),
@ -202,17 +208,14 @@ class ArrayItem extends Component<{
}
}
class TableSetter extends ListSetter {
class TableSetter extends ListSetter {}
}
export default class ArraySetter extends Component<
{
export default class ArraySetter extends Component<{
value: any[];
field: SettingField;
itemConfig?: {
setter?: SetterType;
defaultValue?: any | ((field: SettingField, editor: any) => any);
defaultValue?: any | ((field: SettingField) => any);
required?: boolean;
};
mode?: 'popup' | 'list' | 'table';

View File

@ -3,7 +3,7 @@ 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-pane';
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';
@ -50,7 +50,7 @@ export default class SettingsMainView extends Component {
onMouseOut: hoverNode.bind(null, node, false),
onClick: selectNode.bind(null, node),
};
items.unshift(<Breadcrumb.Item {...props}>{node.title}</Breadcrumb.Item>);
items.unshift(<Breadcrumb.Item {...props} key={node.id}>{node.title}</Breadcrumb.Item>);
node = node.parent;
}
@ -91,7 +91,7 @@ export default class SettingsMainView extends Component {
<div className="lc-settings-main">
{this.renderBreadcrumb()}
<div className="lc-settings-body">
<SettingsTab target={this.main} />
<SettingsPane target={this.main} />
</div>
</div>
);
@ -107,7 +107,7 @@ export default class SettingsMainView extends Component {
>
{(items as SettingField[]).map(field => (
<Tab.Item className="lc-settings-tab-item" title={<Title title={field.title} />} key={field.name}>
<SettingsTab target={field} key={field.id} />
<SettingsPane target={field} key={field.id} />
</Tab.Item>
))}
</Tab>

View File

@ -45,9 +45,9 @@ export interface SettingTarget {
readonly path: string[];
/**
*
*/
readonly top: SettingTarget;
// 响应式自动运行
onEffect(action: () => void): () => void;
// 获取属性值
@ -56,6 +56,12 @@ export interface SettingTarget {
// 设置属性值
setPropValue(propName: string | number, value: any): void;
// 获取附属属性值
getExtraPropValue(propName: string): any;
// 设置附属属性值
setExtraPropValue(propName: string, value: any): void;
/*
// 所有属性值数据
readonly props: object;
@ -74,7 +80,7 @@ export function isCustomView(obj: any): obj is CustomView {
return obj && (isValidElement(obj) || isReactComponent(obj));
}
export type DynamicProps = (field: SettingField, editor: any) => object;
export type DynamicProps = (field: SettingField) => object;
export interface SetterConfig {
/**
@ -102,13 +108,13 @@ export interface FieldExtraProps {
* default value of target prop for setter use
*/
defaultValue?: any;
onChange?: (val: any, field: SettingField, editor: any) => void;
getValue?: (field: SettingField, editor: any) => any;
getValue?: (field: SettingField, fieldValue: any) => any;
setValue?: (field: SettingField, value: any) => void;
/**
* the field conditional show, is not set always true
* @default undefined
*/
condition?: (field: SettingField, editor: any) => boolean;
condition?: (field: SettingField) => boolean;
/**
* default collapsed when display accordion
*/
@ -168,6 +174,7 @@ export class SettingField implements SettingTarget {
readonly nodes: Node[];
readonly componentType: ComponentType | null;
readonly designer: Designer;
readonly top: SettingTarget;
get path() {
const path = this.parent.path.slice();
if (this.type === 'field') {
@ -210,6 +217,7 @@ export class SettingField implements SettingTarget {
this.isOne = parent.isOne;
this.isNone = parent.isNone;
this.designer = parent.designer!;
this.top = parent.top;
// initial items
if (this.type === 'group' && items) {
@ -277,26 +285,25 @@ export class SettingField implements SettingTarget {
*
*/
getValue(): any {
if (this.type !== 'field') {
return null;
let val: any = null;
if (this.type === 'field') {
val = this.parent.getPropValue(this.name);
}
// todo: use getValue
const { getValue } = this.extraProps;
if (getValue) {
return getValue(this, this.editor);
}
return this.parent.getPropValue(this.name);
return getValue ? getValue(this, val) : val;
}
/**
*
*/
setValue(val: any) {
if (this.type !== 'field') {
return;
if (this.type === 'field') {
this.parent.setPropValue(this.name, val);
}
const { setValue } = this.extraProps;
if (setValue) {
setValue(this, val);
}
// todo: use onChange
this.parent.setPropValue(this.name, val);
}
setKey(key: string | number) {
@ -338,6 +345,14 @@ export class SettingField implements SettingTarget {
return this.parent.getPropValue(path);
}
getExtraPropValue(propName: string) {
return this.top.getExtraPropValue(propName);
}
setExtraPropValue(propName: string, value: any) {
this.top.setExtraPropValue(propName, value);
}
purge() {
this.disposeItems();
}
@ -356,6 +371,7 @@ export class SettingsMain implements SettingTarget {
private _componentType: ComponentType | null = null;
private _isSame: boolean = true;
readonly path = [];
readonly top: SettingTarget = this;
get nodes(): Node[] {
return this._nodes;
@ -433,6 +449,9 @@ export class SettingsMain implements SettingTarget {
*
*/
getPropValue(propName: string): any {
if (this.nodes.length < 1) {
return null;
}
return this.nodes[0].getProp(propName, false)?.value;
}
@ -445,6 +464,19 @@ export class SettingsMain implements SettingTarget {
});
}
getExtraPropValue(propName: string) {
if (this.nodes.length < 1) {
return null;
}
return this.nodes[0].getExtraProp(propName, false)?.value;
}
setExtraPropValue(propName: string, value: any) {
this.nodes.forEach(node => {
node.getExtraProp(propName, true)?.setValue(value);
});
}
// 设置多个属性值,替换原有值
setProps(data: object) {
this.nodes.forEach(node => {

View File

@ -11,14 +11,22 @@ import {
DynamicProps,
} from './main';
import { Field, FieldGroup } from './field';
import { TitleContent } from './title';
export type RegisteredSetter = CustomView | {
export type RegisteredSetter = {
component: CustomView;
props?: object;
defaultProps?: object;
title?: TitleContent;
};
const settersMap = new Map<string, RegisteredSetter>();
export function registerSetter(type: string, setter: RegisteredSetter) {
export function registerSetter(type: string, setter: CustomView | RegisteredSetter) {
if (isCustomView(setter)) {
setter = {
component: setter,
title: (setter as any).displayName || (setter as any).name || 'CustomSetter'
};
}
settersMap.set(type, setter);
}
@ -29,15 +37,16 @@ export function getSetter(type: string): RegisteredSetter | null {
export function createSetterContent(setter: any, props: object): ReactNode {
if (typeof setter === 'string') {
setter = getSetter(setter);
if (!isCustomView(setter)) {
if (setter.props) {
props = {
...setter.props,
...props,
};
}
setter = setter.component;
if (!setter) {
return null;
}
if (setter.defaultProps) {
props = {
...setter.defaultProps,
...props,
};
}
setter = setter.component;
}
return createContent(setter, props);
@ -71,12 +80,12 @@ class SettingFieldView extends Component<{ field: SettingField }> {
let firstRun: boolean = true;
this.dispose = field.onEffect(() => {
const state: any = {};
const { extraProps, editor } = field;
const { extraProps } = field;
const { condition, defaultValue } = extraProps;
state.visible = field.isOne && typeof condition === 'function' ? !condition(field, editor) : true;
state.visible = field.isOne && typeof condition === 'function' ? !condition(field) : true;
if (state.visible) {
state.setterProps = {
...(typeof setterProps === 'function' ? setterProps(field, editor) : setterProps),
...(typeof setterProps === 'function' ? setterProps(field) : setterProps),
};
if (field.type === 'field') {
if (defaultValue != null && !('defaultValue' in state.setterProps)) {
@ -136,7 +145,7 @@ class SettingFieldView extends Component<{ field: SettingField }> {
forceInline: extraProps.forceInline,
key: field.id,
// === injection
prop: field,
prop: field, // for compatible
field,
// === IO
value, // reaction point
@ -165,7 +174,7 @@ class SettingGroupView extends Component<{ field: SettingField }> {
let firstRun: boolean = true;
this.dispose = field.onEffect(() => {
const state: any = {};
state.visible = field.isOne && typeof condition === 'function' ? !condition(field, field.editor) : true;
state.visible = field.isOne && typeof condition === 'function' ? !condition(field) : true;
if (state.visible) {
state.items = field.items.slice();
}
@ -216,7 +225,7 @@ export function createSettingFieldView(item: SettingField | CustomView, field: S
return <SettingFieldView field={item} key={item.id} />;
}
} else {
return createContent(item, { key: index, field, editor: field.editor });
return createContent(item, { key: index, field });
}
}

View File

@ -33,6 +33,7 @@
.lc-settings-main {
position: relative;
height: 100%;
.lc-settings-notice {
text-align: center;
@ -64,6 +65,9 @@
&:not(:last-child):hover {
cursor: pointer;
}
.next-breadcrumb-text {
font-size: 12px;
}
}
}
}