big change

This commit is contained in:
kangwei 2020-04-27 02:12:43 +08:00
parent b486a8419c
commit e801a4c47a
141 changed files with 380 additions and 8959 deletions

View File

@ -15,7 +15,9 @@
},
"license": "MIT",
"dependencies": {
"@ali/lowcode-globals": "^0.9.3",
"@ali/lowcode-editor-core": "^0.8.0",
"@ali/lowcode-utils": "^0.8.0",
"@ali/lowcode-types": "^0.8.0",
"classnames": "^2.2.6",
"react": "^16",
"react-dom": "^16.7.0"

View File

@ -11,7 +11,7 @@ import {
import { computed } from '@ali/lowcode-editor-core';
import { Node, ParentalNode } from './document';
import { Designer } from './designer';
import { intl } from './locale';
import { intlNode } from './locale';
import { IconContainer } from './icons/container';
import { IconPage } from './icons/page';
import { IconComponent } from './icons/component';
@ -235,6 +235,38 @@ function preprocessMetadata(metadata: ComponentMetadata): TransformedComponentMe
};
}
export interface MetadataTransducer {
(prev: TransformedComponentMetadata): TransformedComponentMetadata;
/**
* 0 - 9 system
* 10 - 99 builtin-plugin
* 100 - app & plugin
*/
level?: number;
/**
* use to replace TODO
*/
id?: string;
}
const metadataTransducers: MetadataTransducer[] = [];
export function registerMetadataTransducer(transducer: MetadataTransducer, level: number = 100, id?: string) {
transducer.level = level;
transducer.id = id;
const i = metadataTransducers.findIndex((item) => item.level != null && item.level > level);
if (i < 0) {
metadataTransducers.push(transducer);
} else {
metadataTransducers.splice(i, 0, transducer);
}
}
export function getRegisteredMetadataTransducers(): MetadataTransducer[] {
return metadataTransducers;
}
registerMetadataTransducer((metadata) => {
const { configure, componentName } = metadata;
const { component = {} } = configure;
@ -280,7 +312,7 @@ const builtinComponentActions: ComponentAction[] = [
name: 'remove',
content: {
icon: IconRemove,
title: intl('remove'),
title: intlNode('remove'),
action(node: Node) {
node.remove();
},
@ -291,7 +323,7 @@ const builtinComponentActions: ComponentAction[] = [
name: 'copy',
content: {
icon: IconClone,
title: intl('copy'),
title: intlNode('copy'),
action(node: Node) {
// node.remove();
},
@ -309,33 +341,3 @@ export function removeBuiltinComponentAction(name: string) {
export function addBuiltinComponentAction(action: ComponentAction) {
builtinComponentActions.push(action);
}
export interface MetadataTransducer {
(prev: TransformedComponentMetadata): TransformedComponentMetadata;
/**
* 0 - 9 system
* 10 - 99 builtin-plugin
* 100 - app & plugin
*/
level?: number;
/**
* use to replace TODO
*/
id?: string;
}
const metadataTransducers: MetadataTransducer[] = [];
export function registerMetadataTransducer(transducer: MetadataTransducer, level: number = 100, id?: string) {
transducer.level = level;
transducer.id = id;
const i = metadataTransducers.findIndex((item) => item.level != null && item.level > level);
if (i < 0) {
metadataTransducers.push(transducer);
} else {
metadataTransducers.splice(i, 0, transducer);
}
}
export function getRegisteredMetadataTransducers(): MetadataTransducer[] {
return metadataTransducers;
}

View File

@ -2,9 +2,9 @@ import { createIntl } from '@ali/lowcode-editor-core';
import en_US from './en-US.json';
import zh_CN from './zh-CN.json';
const { intl, getLocale, setLocale } = createIntl({
const { intl, intlNode, getLocale, setLocale } = createIntl({
'en-US': en_US,
'zh-CN': zh_CN,
});
export { intl, getLocale, setLocale };
export { intl, intlNode, getLocale, setLocale };

View File

@ -3,8 +3,8 @@
[
"build-plugin-component",
{
"filename": "globals",
"library": "LCEGlobals",
"filename": "core",
"library": "LCECore",
"libraryTarget": "umd"
}
],

View File

@ -1,6 +1,6 @@
{
"name": "@ali/lowcode-globals",
"version": "0.9.3",
"version": "0.8.6",
"description": "Globals api for Ali lowCode engine",
"license": "MIT",
"main": "lib/index.js",
@ -27,13 +27,14 @@
},
"dependencies": {
"@alifd/next": "^1.19.16",
"@ali/lowcode-types": "^0.8.0",
"@ali/lowcode-utils": "^0.8.0",
"@recore/obx": "^1.0.8",
"@recore/obx-react": "^1.0.7",
"classnames": "^2.2.6",
"power-di": "^2.2.4",
"debug": "^4.1.1",
"intl-messageformat": "^8.3.1",
"lodash": "^4.17.15",
"store": "^2.0.12",
"react": "^16",
"react-dom": "^16.7.0"
@ -44,8 +45,6 @@
"@types/node": "^13.7.1",
"@types/react": "^16",
"@types/react-dom": "^16",
"@types/lodash": "^4.14.149",
"@types/store": "^2.0.2",
"build-plugin-component": "^0.2.11",
"build-plugin-fusion": "^0.1.0",

View File

@ -1,3 +1,3 @@
export * from './setter';
export * from './ioc-context';
export * from './tip';
export * from '../widgets/tip/tip';

View File

@ -20,6 +20,8 @@ export type GetReturnType<T, ClsType> = T extends undefined
const NOT_FOUND = Symbol.for('not_found');
import * as utils from './utils';
export class Editor extends EventEmitter implements IEditor {
/**
* Ioc Container
@ -32,6 +34,8 @@ export class Editor extends EventEmitter implements IEditor {
return globalLocale.getLocale();
}
readonly utils = utils;
constructor(readonly config: EditorConfig = {}, readonly components: PluginClassSet = {}) {
super();
}

View File

@ -1,7 +1,7 @@
import { Component } from 'react';
import classNames from 'classnames';
import { intl } from '@ali/lowcode-editor-core';
import { TipConfig } from '@ali/lowcode-types';
import { intl } from '../../intl';
import { resolvePosition } from './utils';
import { tipHandler } from './tip-handler';

View File

@ -20,13 +20,14 @@
],
"author": "xiayang.xy",
"dependencies": {
"@ali/lowcode-editor-core": "^0.8.6",
"@alifd/next": "^1.x",
"prop-types": "^15.5.8",
"@ali/lowcode-editor-core": "^0.8.6",
"@ali/lowcode-designer": "^0.9.2",
"@ali/lowcode-types": "^0.8.0",
"@ali/lowcode-utils": "^0.8.0",
"classnames": "^2.2.6",
"react": "^16.8.1",
"react-dom": "^16.8.1",
"react-router-dom": "^5.1.2",
"store": "^2.0.12"
"react-dom": "^16.8.1"
},
"devDependencies": {
"@alib/build-scripts": "^0.1.3",

View File

@ -1,5 +1,3 @@
@import '../variables.less';
@x-gap: 10px;
@y-gap: 8px;
@ -57,7 +55,7 @@
&.lc-block-field, &.lc-accordion-field {
display: block;
&:not(:first-child) {
border-top: 1px solid var(--color-line-normal, @dark-alpha-2);
border-top: 1px solid var(--color-line-normal);
}
> .lc-field-head {
padding-left: @x-gap;
@ -66,8 +64,8 @@
align-items: center;
font-weight: 500;
background: var(--color-block-background-shallow, rgba(31,56,88,.06));
border-bottom: 1px solid var(--color-line-normal, @dark-alpha-2);
color: var(--color-title, @white-alpha-2);
border-bottom: 1px solid var(--color-line-normal);
color: var(--color-title);
user-select: none;
}
@ -76,7 +74,7 @@
}
+ .lc-inline-field {
border-top: 1px solid var(--color-line-normal, @dark-alpha-2);
border-top: 1px solid var(--color-line-normal);
}
}

View File

@ -1,7 +1,3 @@
export * from './settings-pane';
import './transducers/register';
import './register';
import './style.less';
import SettingsMainView from './settings-primary-view';
export default SettingsMainView;
export * from './settings-primary-pane';
export * from './settings-pane';

View File

@ -1,49 +0,0 @@
{
"name": "@ali/lowcode-plugin-settings-pane",
"version": "0.8.10",
"description": "Settings pane for Ali lowCode engine",
"files": [
"es",
"lib"
],
"main": "lib/index.js",
"module": "es/index.js",
"scripts": {
"build": "build-scripts build --skip-demo",
"test": "ava",
"test:snapshot": "ava --update-snapshots"
},
"dependencies": {
"@ali/lowcode-designer": "^0.9.2",
"@ali/lowcode-editor-core": "^0.8.5",
"@ali/lowcode-globals": "^0.9.2",
"@ali/lowcode-plugin-outline-pane": "^0.8.8",
"@ali/ve-stage-box": "^4.0.0",
"@alifd/next": "^1.19.16",
"classnames": "^2.2.6",
"react": "^16"
},
"devDependencies": {
"@alib/build-scripts": "^0.1.18",
"@types/classnames": "^2.2.7",
"@types/node": "^13.7.1",
"@types/react": "^16",
"build-plugin-component": "^0.2.10",
"build-plugin-fusion": "^0.1.1",
"build-plugin-moment-locales": "^0.1.0"
},
"ava": {
"compileEnhancements": false,
"snapshotDir": "test/fixtures/__snapshots__",
"extensions": [
"ts"
],
"require": [
"ts-node/register"
]
},
"license": "MIT",
"publishConfig": {
"registry": "https://registry.npm.alibaba-inc.com"
}
}

View File

@ -7,7 +7,7 @@ import { SettingsPane } from './settings-pane';
import { createIcon } from '@ali/lowcode-utils';
@observer
export default class SettingsMainView extends Component<{ editor: Editor }> {
export class SettingsPrimaryPane extends Component<{ editor: Editor }> {
private main = new SettingsMain(this.props.editor);
shouldComponentUpdate() {

View File

@ -1,9 +0,0 @@
import { registerMetadataTransducer } from '@ali/lowcode-designer';
import parseProps from './parse-props';
import addonCombine from './addon-combine';
// parseProps
registerMetadataTransducer(parseProps, 10, 'parse-props');
// addon/platform custom
registerMetadataTransducer(addonCombine, 11, 'combine-props');

View File

@ -1,87 +0,0 @@
import { ReactNode, createElement } from 'react';
import { obx } from '@ali/lowcode-editor-core';
import { uniqueId, createContent } from '@ali/lowcode-utils';
import { DockConfig } from "./types";
import { Skeleton } from './skeleton';
import { DockView, WidgetView } from './components/widget-views';
import { IWidget } from './widget/widget';
/**
* /
*/
export default class Dock implements IWidget {
readonly isWidget = true;
readonly id = uniqueId('dock');
readonly name: string;
readonly align?: string;
@obx.ref private _visible: boolean = true;
get visible(): boolean {
return this._visible;
}
get content(): ReactNode {
return createElement(WidgetView, {
widget: this,
key: this.id,
});
}
private inited: boolean = false;
private _body: ReactNode;
get body() {
if (this.inited) {
return this._body;
}
const { props, content, contentProps } = this.config;
if (content) {
this._body = createContent(content, {
...contentProps,
config: this.config,
editor: this.skeleton.editor,
});
} else {
this._body = createElement(DockView, props);
}
return this._body;
}
constructor(readonly skeleton: Skeleton, readonly config: DockConfig) {
const { props = {}, name } = config;
this.name = name;
this.align = props.align;
}
setVisible(flag: boolean) {
if (flag === this._visible) {
return;
}
if (flag) {
this._visible = true;
} else if (this.inited) {
this._visible = false;
}
}
getContent() {
return this.content;
}
getName() {
return this.name;
}
hide() {
this.setVisible(false);
}
show() {
this.setVisible(true);
}
toggle() {
this.setVisible(!this._visible);
}
}

View File

@ -1 +1,7 @@
export { Workbench } from './layouts/workbench';
export * from './skeleton';
export * from './types';
export * from './components/settings';
export * from './components/field';
import './register-defaults';

View File

@ -1,166 +0,0 @@
import { createElement, ReactNode } from 'react';
import { TitleContent } from '@ali/lowcode-types';
import { uniqueId, createContent } from '@ali/lowcode-utils';
import { obx } from '@ali/lowcode-editor-core';
import WidgetContainer from './widget/widget-container';
import { PanelConfig, HelpTipConfig } from './types';
import { TitledPanelView, TabsPanelView, PanelView } from './components/widget-views';
import { Skeleton } from './skeleton';
import { composeTitle } from './widget/utils';
import { IWidget } from './widget/widget';
export default class Panel implements IWidget {
readonly isWidget = true;
readonly name: string;
readonly id: string;
@obx.ref inited: boolean = false;
@obx.ref private _actived: boolean = false;
get actived(): boolean {
return this._actived;
}
get visible(): boolean {
if (this.parent?.visible) {
return this._actived;
}
return false;
}
readonly isPanel = true;
private _body?: ReactNode;
get body() {
this.initBody();
return this._body;
}
get content(): ReactNode {
if (this.plain) {
return createElement(PanelView, {
panel: this,
key: this.id,
});
}
return createElement(TitledPanelView, { panel: this, key: this.id });
}
readonly title: TitleContent;
readonly help?: HelpTipConfig;
private plain: boolean = false;
private container?: WidgetContainer<Panel, PanelConfig>;
private parent?: WidgetContainer;
constructor(readonly skeleton: Skeleton, readonly config: PanelConfig) {
const { name, content, props = {} } = config;
const { hideTitleBar, title, icon, description, help, shortcut } = props;
this.name = name;
this.id = uniqueId(`pane:${name}$`);
this.title = composeTitle(title || name, icon, description);
this.plain = hideTitleBar || !title;
this.help = help;
if (Array.isArray(content)) {
this.container = this.skeleton.createContainer(
name,
(item) => {
if (isPanel(item)) {
return item;
}
return this.skeleton.createPanel(item);
},
true,
() => this.visible,
true,
);
content.forEach((item) => this.add(item));
}
// todo: process shortcut
}
private initBody() {
if (this.inited) {
return;
}
this.inited = true;
if (this.container) {
this._body = createElement(TabsPanelView, {
container: this.container,
});
} else {
const { content, contentProps } = this.config;
this._body = createContent(content, {
...contentProps,
editor: this.skeleton.editor,
config: this.config,
panel: this,
});
}
}
setParent(parent: WidgetContainer) {
if (parent === this.parent) {
return;
}
if (this.parent) {
this.parent.remove(this);
}
this.parent = parent;
}
add(item: Panel | PanelConfig) {
return this.container?.add(item);
}
getPane(name: string): Panel | null {
return this.container?.get(name) || null;
}
remove(item: Panel | string) {
return this.container?.remove(item);
}
active(item?: Panel | string | null) {
this.container?.active(item);
}
getName() {
return this.name;
}
getContent() {
return this.content;
}
setActive(flag: boolean) {
if (flag === this._actived) {
// TODO: 如果移动到另外一个 container会有问题
return;
}
if (flag) {
if (!this.inited) {
this.initBody();
}
this._actived = true;
this.parent?.active(this);
} else if (this.inited) {
this._actived = false;
this.parent?.unactive(this);
}
}
toggle() {
this.setActive(!this._actived);
}
hide() {
this.setActive(false);
}
show() {
this.setActive(true);
}
}
export function isPanel(obj: any): obj is Panel {
return obj && obj.isPanel;
}

View File

@ -1,8 +1,11 @@
import { registerSetter } from '@ali/lowcode-editor-core';
import ArraySetter from '../array-setter';
import ObjectSetter from '../object-setter';
import MixedSetter from '../mixed-setter';
import { registerMetadataTransducer } from '@ali/lowcode-designer';
import ArraySetter from './components/array-setter';
import ObjectSetter from './components/object-setter';
import MixedSetter from './components/mixed-setter';
import { isPlainObject } from '@ali/lowcode-utils';
import parseProps from './transducers/parse-props';
import addonCombine from './transducers/addon-combine';
registerSetter('ArraySetter', {
component: ArraySetter,
@ -28,3 +31,9 @@ registerSetter('ObjectSetter', {
recommend: true,
});
registerSetter('MixedSetter', MixedSetter);
// parseProps
registerMetadataTransducer(parseProps, 10, 'parse-props');
// addon/platform custom
registerMetadataTransducer(addonCombine, 11, 'combine-props');

View File

@ -1,4 +1,4 @@
import { IconType, TitleContent, isI18nData, TipContent } from '@ali/lowcode-globals';
import { IconType, TitleContent, isI18nData, TipContent } from '@ali/lowcode-types';
import { isValidElement } from 'react';
export function composeTitle(title?: TitleContent, icon?: IconType, tip?: TipContent, tipAsTitle?: boolean) {

View File

@ -1,5 +1,6 @@
import { obx, hasOwnProperty, computed } from '@ali/lowcode-globals';
import { obx, computed } from '@ali/lowcode-editor-core';
import { isPanel } from './panel';
import { hasOwnProperty } from '@ali/lowcode-utils';
export interface WidgetItem {
name: string;
}

View File

@ -0,0 +1,30 @@
import { globalContext, Editor } from '@ali/lowcode-editor-core';
import { Designer } from '@ali/lowcode-designer';
import DesignerPlugin from '@ali/lowcode-plugin-designer';
import { Skeleton, SettingsPrimaryPane } from '@ali/lowcode-editor-skeleton';
export const editor = new Editor();
globalContext.register(editor, Editor);
export const skeleton = new Skeleton(editor);
editor.set(Skeleton, skeleton);
export const designer = new Designer({ editor: editor });
editor.set(Designer, designer);
skeleton.add({
area: 'mainArea',
name: 'designer',
type: 'Widget',
content: DesignerPlugin,
});
skeleton.add({
area: 'rightArea',
name: 'settingsPane',
type: 'Panel',
content: SettingsPrimaryPane,
});
export * from '@ali/lowcode-types';
export * from '@ali/lowcode-designer';
export * from '@ali/lowcode-editor-skeleton'

View File

@ -25,6 +25,7 @@
"@ali/iceluna-sdk": "^1.0.6-beta.6",
"@ali/lowcode-designer": "^0.9.3",
"@ali/lowcode-editor-core": "^0.8.6",
"@ali/lowcode-types": "^0.8.0",
"@alifd/next": "^1.19.19",
"react": "^16.8.1"
},

View File

@ -19,13 +19,10 @@ export interface IState {
componentList: object[];
}
export default class ComponentListPlugin extends PureComponent<
PluginProps,
IState
> {
export default class ComponentListPlugin extends PureComponent<PluginProps, IState> {
static displayName = 'LowcodeComponentListPlugin';
constructor(props) {
constructor(props: any) {
super(props);
this.state = {
loading: false,
@ -50,15 +47,15 @@ export default class ComponentListPlugin extends PureComponent<
}
}
transformMaterial = (componentList): any => {
return componentList.map(category => {
transformMaterial = (componentList: any): any => {
return componentList.map((category: any) => {
return {
name: category.title,
items: category.children.map(comp => {
items: category.children.map((comp: any) => {
return {
...comp,
name: comp.componentName,
snippets: comp.snippets.map(snippet => {
snippets: comp.snippets.map((snippet: any) => {
return {
name: snippet.title,
screenshot: snippet.screenshot,
@ -76,7 +73,7 @@ export default class ComponentListPlugin extends PureComponent<
const assets = editor.get('assets') || {};
const list: string[] = [];
const libs: LibrayInfo[] = [];
Object.values(assets.packages).forEach((item): void => {
Object.values(assets.packages).forEach((item: any): void => {
list.push(item.library);
libs.push({
label: item.title,
@ -100,7 +97,7 @@ export default class ComponentListPlugin extends PureComponent<
});
editor.set('dndHelper', {
handleResourceDragStart: function(ev, tagName, schema) {
handleResourceDragStart: function(ev: any, tagName: any, schema: any) {
const designer = editor.get(Designer);
if (designer) {
designer.dragon.boost(
@ -125,18 +122,16 @@ export default class ComponentListPlugin extends PureComponent<
const { searchKey, currentLib, componentList } = this.state;
const libs = currentLib.split(',');
return (componentList || [])
.map(cate => {
.map((cate: any) => {
return {
...cate,
items: (cate.items || []).filter(item => {
let libFlag = libs.some(lib => lib == item.library);
items: (cate.items || []).filter((item: any) => {
let libFlag = libs.some((lib) => lib == item.library);
let keyFlag = true;
if (searchKey) {
keyFlag =
`${item.name || ''} ${item.title || ''}`
.toLowerCase()
.indexOf(searchKey.trim().toLowerCase()) >= 0;
`${item.name || ''} ${item.title || ''}`.toLowerCase().indexOf(searchKey.trim().toLowerCase()) >= 0;
} else {
keyFlag = item.is_show === undefined || !!(item.is_show == 1);
}
@ -144,7 +139,7 @@ export default class ComponentListPlugin extends PureComponent<
}),
};
})
.filter(cate => {
.filter((cate) => {
return cate.items && cate.items.length > 0;
});
};
@ -159,10 +154,10 @@ export default class ComponentListPlugin extends PureComponent<
</div>
<Search
shape="simple"
size="small"
size="medium"
className="search"
placeholder="请输入关键词"
onChange={this.searchAction}
onChange={this.searchAction as any}
onSearch={this.searchAction}
hasClear
/>

View File

@ -0,0 +1 @@
declare module "@ali/iceluna-comp-material-show";

View File

@ -19,7 +19,8 @@
],
"author": "zude.hzd",
"dependencies": {
"@ali/lowcode-editor-core": "^0.8.6",
"@ali/lowcode-types": "^0.8.0",
"@ali/lowcode-editor-core": "^0.8.0",
"@alifd/next": "^1.19.16",
"react": "^16.8.1",
"react-dom": "^16.8.1"

View File

@ -1,11 +1,9 @@
import { Component, isValidElement, ReactElement, ReactNode } from 'react';
import { Dialog, Search, Input } from '@alifd/next';
import { Editor } from '@ali/lowcode-editor-core';
import { PluginProps } from '@ali/lowcode-types';
import './index.scss';
export default class EventBindDialog extends Component<{
editor:Editor,
}> {
export default class EventBindDialog extends Component<PluginProps> {
private eventList: any[] = [
{
name: 'getData',
@ -24,29 +22,29 @@ export default class EventBindDialog extends Component<{
},
];
state = {
visiable:false,
state: any = {
visiable: false,
selectedEventName: '',
eventName: '',
};
openDialog = (bindEventName:String) => {
openDialog = (bindEventName: String) => {
this.setState({
visiable:true,
eventName:bindEventName
})
}
visiable: true,
eventName: bindEventName,
});
};
closeDialog = () => {
this.setState({
visiable:false
})
}
visiable: false,
});
};
componentDidMount (){
const {editor,config} = this.props;
editor.on(`${config.pluginKey}.openDialog`,(bindEventName:String)=>{
this.openDialog(bindEventName)
componentDidMount() {
const { editor, config } = this.props;
editor.on(`${config.pluginKey}.openDialog`, (bindEventName: String) => {
this.openDialog(bindEventName);
});
}
@ -89,22 +87,28 @@ export default class EventBindDialog extends Component<{
onSearchEvent = (searchEventName: String) => {};
onOk = () => {
const {editor} = this.props;
editor.emit('event-setter.bindEvent',this.state.eventName);
const { editor } = this.props;
editor.emit('event-setter.bindEvent', this.state.eventName);
// 选中的是新建事件
if (this.state.selectedEventName == ''){
editor.emit('sourceEditor.addFunction',{
functionName:this.state.eventName
})
if (this.state.selectedEventName == '') {
editor.emit('sourceEditor.addFunction', {
functionName: this.state.eventName,
});
}
this.closeDialog();
};
render() {
const { selectedEventName, eventName,visiable} = this.state;
const { selectedEventName, eventName, visiable } = this.state;
return (
<Dialog visible={visiable} title="事件绑定" onClose={this.closeDialog} onCancel={this.closeDialog} onOk={this.onOk}>
<Dialog
visible={visiable}
title="事件绑定"
onClose={this.closeDialog}
onCancel={this.closeDialog}
onOk={this.onOk}
>
<div className="event-dialog-body">
<div className="dialog-left-container">
<div className="dialog-small-title"></div>

View File

@ -16,7 +16,8 @@
"dependencies": {
"@ali/lowcode-designer": "^0.9.3",
"@ali/lowcode-editor-core": "^0.8.6",
"@ali/lowcode-globals": "^0.9.3",
"@ali/lowcode-utils": "^0.8.0",
"@ali/lowcode-types": "^0.8.0",
"@alifd/next": "^1.19.16",
"classnames": "^2.2.6",
"react": "^16",

View File

@ -0,0 +1,30 @@
import { PureComponent } from 'react';
import { PluginProps } from '@ali/lowcode-types';
import OutlinePane from './pane';
export class OutlineBackupPane extends PureComponent<PluginProps> {
state = {
outlineInited: false,
};
private dispose = this.props.main.onceOutlineVisible(() => {
this.setState({
outlineInited: true,
});
});
componentWillUnmount() {
this.dispose();
}
render() {
if (!this.state.outlineInited) {
return null;
}
return (
<OutlinePane
editor={this.props.main.editor}
config={{
name: '__IN_SETTINGS__',
}}
/>
);
}
}

View File

@ -21,7 +21,6 @@ export default class OutlinePane extends Component<{ config: any; editor: any; i
}
render() {
console.info(this.props);
const tree = this.main.currentTree;
if (!tree) {

View File

@ -1,87 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="0.8.10"></a>
## [0.8.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-settings-pane@0.8.9...@ali/lowcode-plugin-settings-pane@0.8.10) (2020-04-16)
**Note:** Version bump only for package @ali/lowcode-plugin-settings-pane
<a name="0.8.9"></a>
## [0.8.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-settings-pane@0.8.8...@ali/lowcode-plugin-settings-pane@0.8.9) (2020-04-15)
### Bug Fixes
* plugin-designer ([2dfbcd4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/2dfbcd4))
<a name="0.8.8"></a>
## [0.8.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-settings-pane@0.8.7...@ali/lowcode-plugin-settings-pane@0.8.8) (2020-03-31)
**Note:** Version bump only for package @ali/lowcode-plugin-settings-pane
<a name="0.8.7"></a>
## [0.8.7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-settings-pane@0.8.6...@ali/lowcode-plugin-settings-pane@0.8.7) (2020-03-30)
### Bug Fixes
* **settings-pane:** overflow problem ([d2d8556](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/d2d8556))
<a name="0.8.6"></a>
## [0.8.6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-settings-pane@0.8.5...@ali/lowcode-plugin-settings-pane@0.8.6) (2020-03-30)
**Note:** Version bump only for package @ali/lowcode-plugin-settings-pane
<a name="0.8.5"></a>
## [0.8.5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-plugin-settings-pane@0.8.4...@ali/lowcode-plugin-settings-pane@0.8.5) (2020-03-30)
**Note:** Version bump only for package @ali/lowcode-plugin-settings-pane
<a name="0.8.4"></a>
## 0.8.4 (2020-03-30)
### Features
* add color-setter ([a149921](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a149921))
* double outline & ZH_EN support ([b379bd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b379bd7))
* 增加color-setter,json-setter ([93e76ce](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/93e76ce))
<a name="0.8.3"></a>
## 0.8.3 (2020-03-30)
### Features
<<<<<<< HEAD
* add color-setter ([a149921](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a149921))
* double outline & ZH_EN support ([b379bd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b379bd7))
* 增加color-setter,json-setter ([93e76ce](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/93e76ce))
=======
* add color-setter ([a149921](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a14992174b65b1241e7bb82561c7efdfd6589606))
* double outline & ZH_EN support ([b379bd7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b379bd7c0c488ef24f825760750a13d3fa083c96))
* 增加color-setter,json-setter ([93e76ce](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/93e76ce3606603ee926ad83b21b29ffe28dc0682))
>>>>>>> df955e1db90ff104cd11160def80113cfd6faccc

View File

@ -1,15 +0,0 @@
属性面板
其中 field 可独立出去一个包
提供:
1. 快捷设置面板服务
2. 对应节点的设置面板服务
3. 右侧设置面板
4. 提供 setters 服务setters 注册、获取机制
依赖:
tip 处理
field
popup

View File

@ -1,9 +0,0 @@
{
"plugins": [
"build-plugin-component",
"build-plugin-fusion",
["build-plugin-moment-locales", {
"locales": ["zh-cn"]
}]
]
}

View File

@ -1,49 +0,0 @@
{
"name": "@ali/lowcode-plugin-settings-pane",
"version": "0.8.10",
"description": "Settings pane for Ali lowCode engine",
"files": [
"es",
"lib"
],
"main": "lib/index.js",
"module": "es/index.js",
"scripts": {
"build": "build-scripts build --skip-demo",
"test": "ava",
"test:snapshot": "ava --update-snapshots"
},
"dependencies": {
"@ali/lowcode-designer": "^0.9.2",
"@ali/lowcode-editor-core": "^0.8.5",
"@ali/lowcode-globals": "^0.9.2",
"@ali/lowcode-plugin-outline-pane": "^0.8.8",
"@ali/ve-stage-box": "^4.0.0",
"@alifd/next": "^1.19.16",
"classnames": "^2.2.6",
"react": "^16"
},
"devDependencies": {
"@alib/build-scripts": "^0.1.18",
"@types/classnames": "^2.2.7",
"@types/node": "^13.7.1",
"@types/react": "^16",
"build-plugin-component": "^0.2.10",
"build-plugin-fusion": "^0.1.1",
"build-plugin-moment-locales": "^0.1.0"
},
"ava": {
"compileEnhancements": false,
"snapshotDir": "test/fixtures/__snapshots__",
"extensions": [
"ts"
],
"require": [
"ts-node/register"
]
},
"license": "MIT",
"publishConfig": {
"registry": "https://registry.npm.alibaba-inc.com"
}
}

View File

@ -1,184 +0,0 @@
import { Component } from 'react';
import classNames from 'classnames';
import { Icon } from '@alifd/next';
import { Title, TitleContent } from '@ali/lowcode-globals';
import { PopupPipe, PopupContext } from '../popup';
import './index.less';
export interface FieldProps {
className?: string;
title?: TitleContent | null;
defaultDisplay?: 'accordion' | 'inline' | 'block';
collapsed?: boolean;
onExpandChange?: (expandState: boolean) => void;
}
export class Field extends Component<FieldProps> {
state = {
collapsed: this.props.collapsed,
display: this.props.defaultDisplay || 'inline',
};
private toggleExpand = () => {
const { onExpandChange } = this.props;
const collapsed = !this.state.collapsed;
this.setState({
collapsed,
});
onExpandChange && onExpandChange(!collapsed);
};
private body: HTMLDivElement | null = null;
private dispose?: () => void;
private deployBlockTesting() {
if (this.dispose) {
this.dispose();
}
const body = this.body;
if (!body) {
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: true,
attributes: true,
attributeFilter: ['class'],
});
this.dispose = () => observer.disconnect();
}
componentDidMount() {
const { defaultDisplay } = this.props;
if (!defaultDisplay || defaultDisplay === 'inline') {
this.deployBlockTesting();
}
}
componentWillUnmount() {
if (this.dispose) {
this.dispose();
}
}
render() {
const { className, children, title } = this.props;
const { display, collapsed } = this.state;
const isAccordion = display === 'accordion';
return (
<div
className={classNames(`lc-field lc-${display}-field`, className, {
'lc-field-is-collapsed': isAccordion && collapsed,
})}
>
<div className="lc-field-head" onClick={isAccordion ? this.toggleExpand : undefined}>
<div className="lc-field-title">
<Title title={title || ''} />
</div>
{isAccordion && <Icon className="lc-field-icon" type="arrow-up" size="xs" />}
</div>
<div key="body" ref={(shell) => (this.body = shell)} className="lc-field-body">
{children}
</div>
</div>
);
}
}
export interface PopupFieldProps extends FieldProps {
width?: number;
}
export class PopupField extends Component<PopupFieldProps> {
static contextType = PopupContext;
private pipe: any;
static defaultProps: PopupFieldProps = {
width: 300,
};
render() {
const { className, children, title, width } = this.props;
if (!this.pipe) {
this.pipe = (this.context as PopupPipe).create({ width });
}
const titleElement = title && (
<div className="lc-field-title">
<Title title={title} />
</div>
);
this.pipe.send(<div className="lc-field-body">{children}</div>, titleElement);
return (
<div className={classNames('lc-field lc-popup-field', className)}>
{title && (
<div
className="lc-field-head"
onClick={(e) => {
this.pipe.show((e as any).target);
}}
>
<div className="lc-field-title">
<Title title={title} />
</div>
<Icon className="lc-field-icon" type="arrow-left" size="xs" />
</div>
)}
</div>
);
}
}
export interface EntryFieldProps extends FieldProps {
stageName?: string;
}
export class EntryField extends Component<EntryFieldProps> {
render() {
const { stageName, title, className } = this.props;
const classNameList = classNames('engine-setting-field', 'engine-entry-field', className);
const fieldProps: any = {};
if (stageName) {
// 为 stage 切换奠定基础
fieldProps['data-stage-target'] = stageName;
}
const innerElements = [
<span className="engine-field-title" key="field-title">
{title}
</span>,
// renderTip(tip, { propName }),
// <Icons name="arrow" className="engine-field-arrow" size="12px" key="engine-field-arrow-icon" />,
];
return (
<div className={classNameList} {...fieldProps}>
{innerElements}
</div>
);
}
}
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

@ -1,154 +0,0 @@
@import '../variables.less';
@x-gap: 10px;
@y-gap: 8px;
.lc-field {
// head
.lc-field-head {
display: flex;
align-items: center;
justify-content: space-between;
.lc-field-title {
display: flex;
align-items: center;
}
.lc-field-icon {
margin-right: @x-gap;
transform-origin: center;
transition: transform 0.1s;
}
}
&.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 {
display: flex;
align-items: center;
// for top-level style
padding: 8px 10px;
> .lc-field-head {
width: 70px;
margin-right: 1px;
.lc-title-label {
width: 70px;
word-break: break-all;
}
}
> .lc-field-body {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
}
}
&.lc-block-field, &.lc-accordion-field {
display: block;
&:not(:first-child) {
border-top: 1px solid var(--color-line-normal, @dark-alpha-2);
}
> .lc-field-head {
padding-left: @x-gap;
height: 32px;
display: flex;
align-items: center;
font-weight: 500;
background: var(--color-block-background-shallow, rgba(31,56,88,.06));
border-bottom: 1px solid var(--color-line-normal, @dark-alpha-2);
color: var(--color-title, @white-alpha-2);
user-select: none;
}
> .lc-field-body {
padding: @y-gap @x-gap/2;
}
+ .lc-inline-field {
border-top: 1px solid var(--color-line-normal, @dark-alpha-2);
}
}
.lc-setter-actions {
display: flex;
align-items: center;
}
&.lc-block-field {
position: relative;
>.lc-field-body>.lc-block-setter>.lc-setter-actions {
position: absolute;
right: 10px;
top: 0;
height: 32px;
display: flex;
align-items: center;
}
}
&.lc-accordion-field {
// collapsed
&.lc-field-is-collapsed {
> .lc-field-head .lc-field-icon {
transform: rotate(180deg);
}
> .lc-field-body {
display: none;
}
}
// 邻近的保持上下距离
+ .lc-field {
margin-top: @y-gap;
}
}
// 2rd level reset
.lc-field-body {
.lc-inline-field {
padding: @y-gap @x-gap/2 0 @x-gap/2;
&:first-child {
padding-top: 0;
}
+ .lc-accordion-field, +.lc-block-field {
margin-top: @y-gap;
}
}
.lc-field {
border-top: none !important;
}
.lc-accordion-field, .lc-block-field {
> .lc-field-head {
padding-left: @x-gap/2;
background: var(--color-block-background-light);
border-bottom-color: var(--color-line-light);
> .lc-field-icon {
margin-right: @x-gap/2;
}
}
}
// 3rd level field title width should short
.lc-field-body .lc-inline-field {
> .lc-field-head {
width: 50px;
.lc-title-label {
width: 50px;
}
}
}
}
}

View File

@ -1,28 +0,0 @@
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,16 +0,0 @@
import { SVGIcon, IconProps } from "@ali/lowcode-globals";
export function IconConvert(props: IconProps) {
return (
<SVGIcon viewBox="0 0 1024 1024" {...props}>
<path d="M508.16 889.6C291.84 889.6 115.2 714.24 115.2 497.92 115.2 281.6 291.84 106.24 509.44 106.24c43.52 0 85.76 6.4 124.16 20.48l-10.24 30.72c-35.84-11.52-72.96-17.92-113.92-17.92-199.68 0-362.24 161.28-362.24 359.68s162.56 358.4 360.96 358.4 359.68-161.28 359.68-359.68c0-66.56-17.92-131.84-51.2-185.6L844.8 294.4c37.12 60.16 56.32 130.56 56.32 203.52-1.28 216.32-176.64 391.68-392.96 391.68z" />
<path d="M627.2 140.8m-15.36 0a15.36 15.36 0 1 0 30.72 0 15.36 15.36 0 1 0-30.72 0Z" />
<path d="M832 304.64m-15.36 0a15.36 15.36 0 1 0 30.72 0 15.36 15.36 0 1 0-30.72 0Z" />
<path d="M348.16 497.92m-35.84 0a35.84 35.84 0 1 0 71.68 0 35.84 35.84 0 1 0-71.68 0Z" fill="#35A2D4" />
<path d="M508.16 497.92m-35.84 0a35.84 35.84 0 1 0 71.68 0 35.84 35.84 0 1 0-71.68 0Z" fill="#35A2D4" />
<path d="M668.16 497.92m-35.84 0a35.84 35.84 0 1 0 71.68 0 35.84 35.84 0 1 0-71.68 0Z" fill="#35A2D4" />
</SVGIcon>
);
}
IconConvert.displayName = 'Convert';

View File

@ -1,9 +0,0 @@
import { createSettingFieldView } from './settings/settings-pane';
import './transducers/register';
import './setters/register';
import './style.less';
import SettingsMainView from './settings/main-view';
export default SettingsMainView;
export { createSettingFieldView };

View File

@ -1,150 +0,0 @@
import { createContext, ReactNode, Component, PureComponent } from 'react';
import { EventEmitter } from 'events';
import { Balloon } from '@alifd/next';
import { uniqueId } from '@ali/lowcode-globals';
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, actionKey?: string) => {
this.currentId = id;
this.popup(
{
...props,
actionKey,
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<{ actionKey?: string; safeId?: string }> {
private popupPipe = new PopupPipe();
componentWillUnmount() {
this.popupPipe.purge();
}
render() {
const { children, actionKey, safeId } = this.props;
return (
<PopupContext.Provider value={this.popupPipe}>
{children}
<PopupContent key={'pop' + actionKey} safeId={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, actionKey } = 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}
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 actionKey={actionKey} safeId={id}>
{content}
</PopupService>
</div>
</Balloon>
);
}
}

View File

@ -1,22 +0,0 @@
.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;
}
}

View File

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

View File

@ -1,279 +0,0 @@
import { Component, Fragment } from 'react';
import { Icon, Button, Message } from '@alifd/next';
import { Title, SetterType, FieldConfig, SetterConfig } from '@ali/lowcode-globals';
import { createSettingFieldView } from '../../settings/settings-pane';
import { PopupContext, PopupPipe } from '../../popup';
import Sortable from './sortable';
import './style.less';
import { SettingField } from '@ali/lowcode-designer';
interface ArraySetterState {
items: SettingField[];
itemsMap: Map<string | number, SettingField>;
prevLength: number;
}
interface ArraySetterProps {
value: any[];
field: SettingField;
itemSetter?: SetterType;
columns?: FieldConfig[];
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) {
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({
name: i,
setter: props.itemSetter,
// FIXME:
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,
};
}
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) => {
const item = itemsMap.get(id)!;
item.setKey(index);
return item;
});
this.setState({
items,
});
}
private scrollToLast: boolean = false;
onAdd() {
const { items, itemsMap } = this.state;
const { itemSetter } = this.props;
const initialValue = typeof itemSetter === 'object' ? (itemSetter as any).initialValue : null;
const item = this.props.field.createField({
name: items.length,
setter: itemSetter,
// FIXME:
forceInline: 1,
});
items.push(item);
itemsMap.set(item.id, item);
item.setValue(typeof initialValue === 'function' ? initialValue(item) : initialValue);
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() {
let columns: any = null;
if (this.props.columns) {
columns = this.props.columns.map((column) => <Title key={column.name} title={column.title || (column.name as string)} />);
}
const { items } = this.state;
const scrollToLast = this.scrollToLast;
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">
<Button size="medium" onClick={this.onAdd.bind(this)}>
<Icon type="add" />
<span></span>
</Button>
</div>*/}
{columns && <div className="lc-setter-list-columns">{columns}</div>}
{content}
<Button className="lc-setter-list-add" type="primary" onClick={this.onAdd.bind(this)}>
<Icon type="add" />
<span></span>
</Button>
</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 {
// todo:
// forceInline = 1
// has more actions
}
export default class ArraySetter extends Component<{
value: any[];
field: SettingField;
itemSetter?: SetterType;
mode?: 'popup' | 'list';
forceInline?: boolean;
multiValue?: boolean;
}> {
static contextType = PopupContext;
private pipe: any;
render() {
const { mode, forceInline, ...props } = this.props;
const { field, itemSetter } = props;
let columns: FieldConfig[] | undefined;
if ((itemSetter as SetterConfig)?.componentName === 'ObjectSetter') {
const items: FieldConfig[] = (itemSetter as any).props?.config?.items;
if (items && Array.isArray(items)) {
columns = items.filter((item) => item.isRequired || item.important || (item.setter as any)?.isRequired);
if (columns.length > 4) {
columns = columns.slice(0, 4);
}
}
}
if (mode === 'popup' || forceInline) {
const title = (
<Fragment>
<Title title={field.title} />
</Fragment>
);
if (!this.pipe) {
let width = 360;
if (columns) {
if (columns.length === 3) {
width = 480;
} else if (columns.length > 3) {
width = 600;
}
}
this.pipe = (this.context as PopupPipe).create({ width });
}
this.pipe.send(<TableSetter key={field.id} {...props} columns={columns} />, title);
return (
<Button
type={forceInline ? 'normal' : 'primary'}
onClick={(e) => {
this.pipe.show((e as any).target, field.id);
}}
>
<Icon type="edit" />
{forceInline ? title : '编辑数组'}
</Button>
);
} else {
return <ListSetter {...props} columns={columns?.slice(0, 2)} />;
}
}
}

View File

@ -1,29 +0,0 @@
.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;
}
}

View File

@ -1,220 +0,0 @@
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;

View File

@ -1,103 +0,0 @@
.lc-setter-list {
[draggable] {
cursor: move;
}
color: var(--color-text);
.next-btn {
display: inline-flex;
align-items: center;
line-height: 1 !important;
max-width: 100%;
text-overflow: ellipsis;
}
.lc-setter-list-add {
display: block;
width: 100%;
margin-top: 8px;;
}
.lc-setter-list-columns {
display: flex;
> .lc-title {
flex: 1;
justify-content: center;
}
margin-left: 47px;
margin-right: 28px;
margin-bottom: 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;
&:not(:last-child) {
margin-bottom: 5px;
}
.lc-listitem {
position: relative;
outline: none;
display: flex;
align-items: stretch;
height: 34px;
.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: stretch;
overflow: hidden;
min-width: 0;
text-overflow: ellipsis;
.lc-field {
padding: 0 !important;
display: flex;
align-items: center;
>.lc-field-body {
justify-content: center;
}
}
> * {
width: 100%;
}
.next-btn {
display: block;
width: 100%;
}
}
.lc-listitem-handler {
margin-left: 2px;
display: inline-flex;
align-items: center;
.next-icon-ellipsis {
transform: rotate(90deg);
}
opacity: 0.6;
}
}
}
}

View File

@ -1,257 +0,0 @@
import React, { Component, isValidElement } from 'react';
import classNames from 'classnames';
import { Dropdown, Button, Menu } from '@alifd/next';
import {
getSetter,
getSettersMap,
SetterConfig,
computed,
obx,
CustomView,
DynamicProps,
DynamicSetter,
TitleContent,
isSetterConfig,
Title,
createSetterContent,
observer,
isDynamicSetter,
shallowIntl,
EmbedTip,
isI18nData,
} from '@ali/lowcode-globals';
import { IconConvert } from '../../icons/convert';
import './style.less';
import { SettingField } from '@ali/lowcode-designer';
export interface SetterItem {
name: string;
title: TitleContent;
setter: string | DynamicSetter | CustomView;
props?: object | DynamicProps;
condition?: (field: SettingField) => boolean;
initialValue?: any | ((field: SettingField) => any);
list: boolean;
}
function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | DynamicSetter>): SetterItem[] {
if (!setters) {
const normalized: SetterItem[] = [];
getSettersMap().forEach((setter, name) => {
if (name === 'MixedSetter') {
return;
}
normalized.push({
name,
title: setter.title || name,
setter: name,
condition: setter.condition,
initialValue: setter.initialValue,
list: setter.recommend || false,
});
});
return normalized;
}
const names: string[] = [];
function generateName(n: string) {
let idx = 1;
let got = n;
while (names.indexOf(got) > -1) {
got = `${n}:${idx++}`;
}
names.push(got);
return got;
}
return setters.map((setter) => {
const config: any = {
setter,
list: true,
};
if (isSetterConfig(setter)) {
config.setter = setter.componentName;
config.props = setter.props;
config.condition = setter.condition;
config.initialValue = setter.initialValue;
config.title = setter.title;
}
if (typeof config.setter === 'string') {
config.name = config.setter;
names.push(config.name);
const info = getSetter(config.setter);
if (!config.title) {
config.title = info?.title || config.setter;
}
if (!config.condition) {
config.condition = info?.condition;
}
if (!config.initialValue) {
config.initialValue = info?.initialValue;
}
} else {
config.name = generateName((config.setter as any).displayName || (config.setter as any).name || 'CustomSetter');
if (!config.title) {
config.title = config.name;
}
}
return config;
});
}
@observer
export default class MixedSetter extends Component<{
field: SettingField;
setters?: Array<string | SetterConfig | CustomView | DynamicSetter>;
onSetterChange?: (field: SettingField, name: string) => void;
onChange?: (val: any) => void;
value?: any;
className?: string;
}> {
private setters = nomalizeSetters(this.props.setters);
@obx.ref private used?: string;
@computed private getCurrentSetter() {
const { field } = this.props;
let firstMatched: SetterItem | undefined;
for (const setter of this.setters) {
const matched = !setter.condition || setter.condition(field);
if (matched) {
if (setter.name === this.used) {
return setter;
}
if (!firstMatched) {
firstMatched = setter;
}
}
}
return firstMatched;
}
private useSetter = (name: string) => {
if (name === this.used) {
return;
}
const { field, onChange } = this.props;
const setter = this.setters.find((item) => item.name === name);
this.used = name;
if (setter) {
let newValue: any = setter.initialValue;
if (newValue && typeof newValue === 'function') {
newValue = newValue(field);
}
onChange && onChange(newValue);
}
};
private shell: HTMLDivElement | null = null;
private checkIsBlockField() {
if (this.shell) {
const setter = this.shell.firstElementChild;
if (setter && setter.classList.contains('lc-block-setter')) {
this.shell.classList.add('lc-block-setter');
} else {
this.shell.classList.remove('lc-block-setter');
}
}
}
componentDidUpdate() {
this.checkIsBlockField();
}
componentDidMount() {
this.checkIsBlockField();
}
render() {
const { className, field, setters, onSetterChange, ...restProps } = this.props;
const currentSetter = this.getCurrentSetter();
const isTwoType = this.setters.length < 3;
let setterContent: any;
const triggerTitle: any = {
tip: {
type: 'i18n',
'zh-CN': '切换格式',
'en-US': 'Switch Format',
},
icon: <IconConvert size={24} />,
};
if (currentSetter) {
const { setter, title, props } = currentSetter;
let setterProps: any = {};
let setterType: any;
if (isDynamicSetter(setter)) {
setterType = setter.call(field, field);
} else {
setterType = setter;
}
if (props) {
setterProps = props;
if (typeof setterProps === 'function') {
setterProps = setterProps(field);
}
}
setterContent = createSetterContent(setterType, {
...shallowIntl(setterProps),
field,
...restProps,
});
if (title) {
if (typeof title !== 'object' || isI18nData(title) || isValidElement(title)) {
triggerTitle.tip = title;
} else {
triggerTitle.tip = title.tip || title.label;
}
}
} else {
// 未匹配的 null 值,显示 NullValue 空值
// 未匹配的 其它 值,显示 InvalidValue 非法值
if (restProps.value == null) {
setterContent = <span>NullValue</span>;
} else {
setterContent = <span>InvalidValue</span>;
}
}
const usedName = currentSetter?.name || this.used;
let moreBtnNode = (
<Title
title={triggerTitle}
className="lc-switch-trigger"
onClick={
isTwoType
? () => {
if (this.setters[0]?.name === usedName) {
this.useSetter(this.setters[1]?.name);
} else {
this.useSetter(this.setters[0]?.name);
}
}
: undefined
}
/>
);
if (!isTwoType) {
moreBtnNode = (
<Dropdown trigger={moreBtnNode} triggerType="click" align="tr br">
<Menu selectMode="single" hasSelectedIcon={true} selectedKeys={usedName} onItemClick={this.useSetter}>
{this.setters.filter(setter => setter.list || setter.name === usedName).map((setter) => {
return (
<Menu.Item key={setter.name}>
<Title title={setter.title} />
</Menu.Item>
);
})}
</Menu>
</Dropdown>
);
}
return (
<div ref={(shell) => (this.shell = shell)} className={classNames('lc-setter-mixed', className)}>
{setterContent}
<div className="lc-setter-actions">{moreBtnNode}</div>
</div>
);
}
}

View File

@ -1,30 +0,0 @@
.lc-setter-mixed {
flex: 1;
min-width: 0;
margin-right: 26px;
display: block;
position: relative;
>.lc-setter-actions {
position: absolute;
right: -2px;
top: 50%;
transform: translate(100%, -50%);
.lc-switch-trigger {
cursor: pointer;
opacity: 0.6;
&:hover {
opacity: 1;
}
}
}
.next-input,.next-date-picker {
width: 100%;
}
&.lc-block-setter {
position: static;
margin-right: 0;
>.lc-setter-actions {
transform: none;
}
}
}

View File

@ -1,180 +0,0 @@
import { Component, Fragment } from 'react';
import { Icon, Button } from '@alifd/next';
import { Title, SetterType, FieldConfig } from '@ali/lowcode-globals';
import { createSettingFieldView } from '../../settings/settings-pane';
import { PopupContext, PopupPipe } from '../../popup';
import { SettingField } from '@ali/lowcode-designer';
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 {...props} />;
}
}
}
interface ObjectSetterConfig {
items?: FieldConfig[];
extraSetter?: SetterType;
}
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.isRequired || conf.important || (conf.setter as any)?.isRequired) {
const item = field.createField({
...conf,
// in column-cell
forceInline: 3,
});
items.push(item);
}
}
}
if (items.length > 0) {
this.items = items;
}
let firstRun: boolean = true;
field.onEffect(() => {
let state: any = {};
if (descriptor) {
if (typeof descriptor === 'function') {
state.descriptor = descriptor(field);
} else {
state.descriptor = field.getPropValue(descriptor);
}
} else {
state.descriptor = field.title;
}
if (firstRun) {
firstRun = false;
this.state = state;
} else {
this.setState(state);
}
});
}
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, config } = 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} field={field} config={config} />, 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, field.id);
}}
>
<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, field.id);
}}
>
<Icon type="edit" />
{title}
</Button>
);
}
}
interface FormSetterProps {
field: SettingField;
config: ObjectSetterConfig;
}
class FormSetter extends Component<FormSetterProps> {
private items: SettingField[];
constructor(props: RowSetterProps) {
super(props);
const { config, field } = props;
this.items = (config.items || []).map((conf) => field.createField(conf));
// TODO: extraConfig for custom fields
}
shouldComponentUpdate() {
return false;
}
render() {
const { field } = this.props;
return (
<div className="lc-setter-object lc-block-setter">
{this.items.map((item, index) => createSettingFieldView(item, field, index))}
</div>
);
}
}

View File

@ -1,31 +0,0 @@
.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;
}
}
}

View File

@ -1,29 +0,0 @@
import { registerSetter, isPlainObject } from '@ali/lowcode-globals';
import ArraySetter from './array-setter';
import ObjectSetter from './object-setter';
import MixedSetter from './mixed-setter';
registerSetter('ArraySetter', {
component: ArraySetter,
defaultProps: {},
title: 'ArraySetter', // TODO
condition: (field: any) => {
const v = field.getValue();
return v == null || Array.isArray(v);
},
initialValue: [],
recommend: true,
});
registerSetter('ObjectSetter', {
component: ObjectSetter,
// todo: defaultProps
defaultProps: {},
title: 'ObjectSetter', // TODO
condition: (field: any) => {
const v = field.getValue();
return v == null || isPlainObject(v);
},
initialValue: {},
recommend: true,
});
registerSetter('MixedSetter', MixedSetter);

View File

@ -1,148 +0,0 @@
import React, { Component, PureComponent } from 'react';
import { Tab, Breadcrumb } from '@alifd/next';
import { Title, createIcon, observer } from '@ali/lowcode-globals';
import { Node, isSettingField, SettingField } from '@ali/lowcode-designer';
import { Pane as OutlinePane } from '@ali/lowcode-plugin-outline-pane';
import Editor from '@ali/lowcode-editor-core';
import { SettingsMain } from './main';
import SettingsPane from './settings-pane';
@observer
export default class SettingsMainView extends Component<{ editor: Editor }> {
private main = new SettingsMain(this.props.editor);
shouldComponentUpdate() {
return false;
}
componentWillUnmount() {
this.main.purge();
}
renderBreadcrumb() {
const { settings } = this.main;
if (!settings) {
return null;
}
if (settings.isMultiple) {
return (
<div className="lc-settings-navigator">
{createIcon(settings.componentMeta?.icon, { className: 'lc-settings-navigator-icon'})}
<Title title={settings.componentMeta!.title} />
<span>x {settings.nodes.length}</span>
</div>
);
}
let node: Node | null = settings.first;
const items = [];
let l = 3;
while (l-- > 0 && node) {
const props =
l === 2
? {}
: {
onMouseOver: hoverNode.bind(null, node, true),
onMouseOut: hoverNode.bind(null, node, false),
onClick: selectNode.bind(null, node),
};
items.unshift(<Breadcrumb.Item {...props} key={node.id}><Title title={node.title} /></Breadcrumb.Item>);
node = node.parent;
}
return (
<div className="lc-settings-navigator">
{createIcon(this.main.componentMeta?.icon, { className: 'lc-settings-navigator-icon'})}
<Breadcrumb className="lc-settings-node-breadcrumb">{items}</Breadcrumb>
</div>
);
}
render() {
const { settings } = this.main;
if (!settings) {
// 未选中节点,提示选中 或者 显示根节点设置
return (
<div className="lc-settings-main">
<OutlinePaneEntry main={this.main} />
<div className="lc-settings-notice">
<p></p>
</div>
</div>
);
}
if (!settings.isSameComponent) {
// todo: future support 获取设置项交集编辑
return (
<div className="lc-settings-main">
<OutlinePaneEntry main={this.main} />
<div className="lc-settings-notice">
<p></p>
</div>
</div>
);
}
const { items } = settings;
if (items.length > 5 || items.some(item => !isSettingField(item) || !item.isGroup)) {
return (
<div className="lc-settings-main">
<OutlinePaneEntry main={this.main} />
{this.renderBreadcrumb()}
<div className="lc-settings-body">
<SettingsPane target={settings} />
</div>
</div>
);
}
return (
<div className="lc-settings-main">
<OutlinePaneEntry main={this.main} />
<Tab
navClassName="lc-settings-tabs"
animation={false}
excessMode="dropdown"
contentClassName="lc-settings-tabs-content"
extra={this.renderBreadcrumb()}
>
{(items as SettingField[]).map(field => (
<Tab.Item className="lc-settings-tab-item" title={<Title title={field.title} />} key={field.name}>
<SettingsPane target={field} key={field.id} />
</Tab.Item>
))}
</Tab>
</div>
);
}
}
class OutlinePaneEntry extends PureComponent<{ main: SettingsMain }> {
state = {
outlineInited: false,
};
private dispose = this.props.main.onceOutlineVisible(() => {
this.setState({
outlineInited: true,
});
});
componentWillUnmount() {
this.dispose();
}
render() {
if (!this.state.outlineInited) {
return null;
}
return <OutlinePane editor={this.props.main.editor} config={{
name: '__IN_SETTINGS__'
}} />;
}
}
function hoverNode(node: Node, flag: boolean) {
node.hover(flag);
}
function selectNode(node: Node) {
node.select();
}

View File

@ -1,89 +0,0 @@
import { EventEmitter } from 'events';
import { obx, computed } from '@ali/lowcode-globals';
import { Node, Designer, Selection, SettingTopEntry } from '@ali/lowcode-designer';
import { getTreeMaster } from '@ali/lowcode-plugin-outline-pane';
import Editor from '@ali/lowcode-editor-core';
function generateSessionId(nodes: Node[]) {
return nodes
.map((node) => node.id)
.sort()
.join(',');
}
export class SettingsMain {
private emitter = new EventEmitter();
private _sessionId = '';
@obx.ref private _settings?: SettingTopEntry;
@computed get length(): number | undefined {
return this._settings?.nodes.length;
}
@computed get componentMeta() {
return this._settings?.componentMeta;
}
get settings() {
return this._settings;
}
private disposeListener: () => void;
private designer?: Designer;
constructor(readonly editor: Editor) {
this.init();
}
private async init() {
const setupSelection = (selection?: Selection) => {
if (selection) {
this.setup(selection.getNodes());
} else {
this.setup([]);
}
};
this.editor.on('designer.selection.change', setupSelection);
this.disposeListener = () => {
this.editor.removeListener('designer.selection.change', setupSelection);
};
const designer = await this.editor.onceGot(Designer);
this.designer = designer;
getTreeMaster(designer).onceEnableBuiltin(() => {
this.emitter.emit('outline-visible');
});
setupSelection(designer.currentSelection);
}
private setup(nodes: Node[]) {
// check nodes change
const sessionId = generateSessionId(nodes);
if (sessionId === this._sessionId) {
return;
}
this._sessionId = sessionId;
if (nodes.length < 1) {
this._settings = undefined;
return;
}
if (!this.designer) {
this.designer = nodes[0].document.designer;
}
this._settings = this.designer.createSettingEntry(this.editor, nodes);
}
onceOutlineVisible(fn: () => void): () => void {
this.emitter.on('outline-visible', fn);
return () => {
this.emitter.removeListener('outline-visible', fn);
};
}
purge() {
this.disposeListener();
this.emitter.removeAllListeners();
}
}

View File

@ -1,149 +0,0 @@
import { Component } from 'react';
import {
createContent,
CustomView,
intl,
shallowIntl,
isSetterConfig,
createSetterContent,
observer,
} from '@ali/lowcode-globals';
import { Field, createField } from '../field';
import PopupService from '../popup';
import { SettingField, isSettingField, SettingTopEntry, SettingEntry } from '@ali/lowcode-designer';
@observer
class SettingFieldView extends Component<{ field: SettingField }> {
render() {
const { field } = this.props;
const { extraProps } = field;
const { condition, defaultValue } = extraProps;
const visible = field.isSingle && typeof condition === 'function' ? condition(field) !== false : true;
if (!visible) {
return null;
}
const { setter } = field;
let setterProps: any = {};
let setterType: any;
if (Array.isArray(setter)) {
setterType = 'MixedSetter';
setterProps = {
setters: setter,
};
} else if (isSetterConfig(setter)) {
setterType = setter.componentName;
if (setter.props) {
setterProps = setter.props;
if (typeof setterProps === 'function') {
setterProps = setterProps(field);
}
}
} else if (setter) {
setterType = setter;
}
let value = null;
if (field.type === 'field') {
if (defaultValue != null && !('defaultValue' in setterProps)) {
setterProps.defaultValue = defaultValue;
}
if (field.valueState > 0) {
value = field.getValue();
} else {
setterProps.multiValue = true;
if (!('placeholder' in setterProps)) {
// FIXME! move to locale file
setterProps.placeholder = intl({
type: 'i18n',
'zh-CN': '多种值',
'en-US': 'Multiple Value',
});
}
}
}
// todo: error handling
return createField({
title: field.title,
collapsed: !field.expanded,
onExpandChange: (expandState) => field.setExpanded(expandState),
}, createSetterContent(setterType, {
...shallowIntl(setterProps),
forceInline: extraProps.forceInline,
key: field.id,
// === injection
prop: field, // for compatible vision
field,
// === IO
value, // reaction point
onChange: (value: any) => {
this.setState({
value,
});
field.setValue(value);
},
}), extraProps.forceInline ? 'plain' : extraProps.display);
}
}
@observer
class SettingGroupView extends Component<{ field: SettingField }> {
shouldComponentUpdate() {
return false;
}
render() {
const { field } = this.props;
const { extraProps } = field;
const { condition } = extraProps;
const visible = field.isSingle && typeof condition === 'function' ? condition(field) !== false : true;
if (!visible) {
return null;
}
// todo: split collapsed state | field.items for optimize
return (
<Field defaultDisplay="accordion" title={field.title} collapsed={!field.expanded} onExpandChange={(expandState) => {
field.setExpanded(expandState);
}}>
{field.items.map((item, index) => createSettingFieldView(item, field, index))}
</Field>
);
}
}
export function createSettingFieldView(item: SettingField | CustomView, field: SettingEntry, index?: number) {
if (isSettingField(item)) {
if (item.isGroup) {
return <SettingGroupView field={item} key={item.id} />;
} else {
return <SettingFieldView field={item} key={item.id} />;
}
} else {
return createContent(item, { key: index, field });
}
}
@observer
export default class SettingsPane extends Component<{ target: SettingTopEntry | SettingField }> {
shouldComponentUpdate() {
return false;
}
render() {
const { target } = this.props;
const items = target.items
return (
<div className="lc-settings-pane">
{/* todo: add head for single use */}
<PopupService>
<div className="lc-settings-content">
{items.map((item, index) => createSettingFieldView(item, target, index))}
</div>
</PopupService>
</div>
);
}
}

View File

@ -1,124 +0,0 @@
.lc-settings-main {
position: relative;
height: 100%;
overflow: hidden;
.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));
padding: 50px 15px 0;
}
.lc-settings-navigator {
height: 30px;
display: flex;
align-items: center;
padding-left: 5px;
border-bottom: 1px solid var(--color-line-normal);
.lc-settings-navigator-icon {
width: 16px;
height: 16px;
* {
fill: var(--color-icon-normal, rgba(31, 56, 88, 0.4));
}
}
.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;
}
.next-breadcrumb-text {
font-size: 12px;
}
}
}
}
.lc-settings-body {
position: absolute;
top: 30px;
right: 0;
left: 0;
bottom: 0;
overflow-y: auto;
}
// ====== reset fusion-tabs =====
.lc-settings-tabs {
position: relative;
overflow: visible;
> .next-tabs-nav-extra {
position: absolute !important;
top: 40px !important;
left: 0 !important;
height: 30px;
right: 0;
transform: none !important;
}
.next-tabs-nav-container {
.next-tabs-nav {
display: flex;
.next-tabs-tab.lc-settings-tab-item {
flex: 1;
min-width: 0;
outline: none;
.next-tabs-tab-inner {
text-align: center;
padding: 12px 0;
}
}
}
}
}
.lc-settings-tabs-content {
position: absolute;
top: 70px;
left:0;
right: 0;
bottom: 0;
.next-tabs-tabpane {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
overflow-y: auto;
outline: none !important;
box-shadow: none !important;
}
}
.lc-outline-pane {
position: absolute;
z-index: 100;
background-color: white;
top: 0;
bottom: 0;
display: none;
}
}
.lc-settings-pane {
padding-bottom: 50px;
.next-btn {
line-height: 1 !important;
}
}
html.lc-cursor-dragging:not(.lowcode-has-fixed-tree) {
.lc-settings-main .lc-outline-pane {
display: block;
}
}

View File

@ -1,256 +0,0 @@
import { TransformedComponentMetadata, FieldConfig } from '@ali/lowcode-globals';
import { SettingField } from '../settings/setting-field';
export default function(metadata: TransformedComponentMetadata): TransformedComponentMetadata {
const { componentName, configure = {} } = metadata;
if (componentName === 'Leaf') {
return {
...metadata,
configure: {
...configure,
combined: [
{
name: 'children',
title: { type: 'i18n', 'zh-CN': '内容设置', 'en-US': 'Content' },
setter: {
componentName: 'MixinSetter',
props: {
// TODO:
setters: [
{
componentName: 'StringSetter',
props: {
// TODO: textarea mode
multiline: true,
},
initialValue: '',
},
{
componentName: 'ExpressionSetter',
initialValue: {
type: 'JSExpression',
value: '',
},
},
],
},
},
},
],
},
};
}
const { props, events = {}, styles } = configure as any;
const isRoot: boolean = componentName === 'Page' || componentName === 'Component';
const eventsDefinition: any[] = [];
const supportedLifecycles =
events.supportedLifecycles ||
(isRoot
? [
{
description: '初始化时',
name: 'constructor',
},
{
description: '装载后',
name: 'componentDidMount',
},
{
description: '更新时',
name: 'componentDidMount',
},
{
description: '卸载时',
name: 'componentWillUnmount',
},
]
: null);
if (supportedLifecycles) {
eventsDefinition.push({
type: 'lifeCycleEvent',
title: '生命周期',
list: supportedLifecycles.map((event: any) => (typeof event === 'string' ? { name: event } : event)),
});
}
if (events.supportedEvents) {
eventsDefinition.push({
type: 'events',
title: '事件',
list: (events.supportedEvents || []).map((event: any) => (typeof event === 'string' ? { name: event } : event)),
});
}
// 通用设置
const propsGroup = props || [];
propsGroup.push({
name: '#generals',
title: { type: 'i18n', 'zh-CN': '通用', 'en-US': 'General' },
items: [
{
name: 'id',
title: 'ID',
setter: 'StringSetter',
},
{
name: 'key',
title: 'Key',
// todo: use Mixin
setter: 'StringSetter',
},
{
name: 'ref',
title: 'Ref',
setter: 'StringSetter',
},
/*
{
name: '!more',
title: '更多',
setter: 'PropertiesSetter',
},*/
],
});
const combined: FieldConfig[] = [
{
title: { type: 'i18n', 'zh-CN': '属性', 'en-US': 'Props' },
name: '#props',
items: propsGroup,
},
];
const stylesGroup: FieldConfig[] = [];
if (styles?.supportClassName) {
stylesGroup.push({
name: 'className',
title: { type: 'i18n', 'zh-CN': '类名绑定', 'en-US': 'ClassName' },
setter: 'ClassNameSetter',
});
}
if (styles?.supportInlineStyle) {
stylesGroup.push({
name: 'style',
title: { type: 'i18n', 'zh-CN': '行内样式', 'en-US': 'Style' },
setter: 'StyleSetter',
});
}
if (stylesGroup.length > 0) {
combined.push({
name: '#styles',
title: { type: 'i18n', 'zh-CN': '样式', 'en-US': 'Styles' },
items: stylesGroup,
});
}
if (eventsDefinition.length > 0) {
combined.push({
name: '#events',
title: { type: 'i18n', 'zh-CN': '事件', 'en-US': 'Events' },
items: [
{
name: '!events',
title: { type: 'i18n', 'zh-CN': '事件设置', 'en-US': 'Events' },
setter: {
componentName: 'EventsSetter',
props: {
definition: eventsDefinition,
},
},
getValue(field: SettingField, val?: any[]) {
// todo:
return val;
},
setValue(field: SettingField, eventDataList: any[]) {
// todo:
return;
},
},
],
});
}
if (isRoot) {
/*
combined.push({
name: '#advanced',
title: { type: 'i18n', 'zh-CN': '高级', 'en-US': 'Advance' },
items: [],
});
*/
} else {
combined.push({
name: '#advanced',
title: { type: 'i18n', 'zh-CN': '高级', 'en-US': 'Advance' },
items: [
{
name: '__condition',
title: { type: 'i18n', 'zh-CN': '条件显示', 'en-US': 'Condition' },
setter: 'ExpressionSetter',
},
{
name: '#loop',
title: { type: 'i18n', 'zh-CN': '循环', 'en-US': 'Loop' },
items: [
{
name: '__loop',
title: { type: 'i18n', 'zh-CN': '循环数据', 'en-US': 'Loop Data' },
setter: {
componentName: 'MixinSetter',
props: {
// TODO:
setters: [
{
componentName: 'JSONSetter',
props: {
mode: 'popup',
placeholder: { type: 'i18n', 'zh-CN': '编辑数据', 'en-US': 'Edit Data' },
},
},
{
componentName: 'ExpressionSetter',
props: {
placeholder: { type: 'i18n', 'zh-CN': '绑定数据', 'en-US': 'Bind Data' },
},
},
],
},
},
},
{
name: '__loopArgs.0',
title: { type: 'i18n', 'zh-CN': '迭代变量名', 'en-US': 'Loop Item' },
setter: {
componentName: 'StringSetter',
props: {
placeholder: { type: 'i18n', 'zh-CN': '默认为: item', 'en-US': 'Defaults: item' },
}
},
},
{
name: '__loopArgs.1',
title: { type: 'i18n', 'zh-CN': '索引变量名', 'en-US': 'Loop Index' },
setter: {
componentName: 'StringSetter',
props: {
placeholder: { type: 'i18n', 'zh-CN': '默认为: index', 'en-US': 'Defaults: index' },
}
},
},
{
name: 'key',
title: 'Key',
setter: 'ExpressionSetter',
},
],
},
],
});
}
return {
...metadata,
configure: {
...configure,
combined,
},
};
}

View File

@ -1,232 +0,0 @@
import {
FieldConfig,
PropConfig,
PropType,
SetterType,
OneOf,
Shape,
ObjectOf,
ArrayOf,
TransformedComponentMetadata,
} from '@ali/lowcode-globals';
function propConfigToFieldConfig(propConfig: PropConfig): FieldConfig {
const { name, description } = propConfig;
const title = {
label: {
type: 'i18n',
'en-US': name,
'zh-CN': description?.slice(0, 10) || name,
},
tip: description ? `${name} | ${description}` : undefined,
};
return {
title,
...propConfig,
setter: propTypeToSetter(propConfig.propType),
};
}
function propTypeToSetter(propType: PropType): SetterType {
let typeName: string;
let isRequired: boolean | undefined = false;
if (typeof propType === 'string') {
typeName = propType;
} else {
typeName = propType.type;
isRequired = propType.isRequired;
}
// TODO: use mixinSetter wrapper
switch (typeName) {
case 'string':
return {
componentName: 'StringSetter',
isRequired,
initialValue: '',
};
case 'number':
return {
componentName: 'NumberSetter',
isRequired,
initialValue: 0,
};
case 'bool':
return {
componentName: 'NumberSetter',
isRequired,
initialValue: false,
};
case 'oneOf':
const dataSource = ((propType as OneOf).value || []).map((value, index) => {
const t = typeof value;
return {
label: t === 'string' || t === 'number' || t === 'boolean' ? String(value) : `value ${index}`,
value,
};
});
const componentName = dataSource.length > 4 ? 'SelectSetter' : 'RadioGroupSetter';
return {
componentName,
props: { dataSource },
isRequired,
initialValue: dataSource[0] ? dataSource[0].value : null,
};
case 'element':
case 'node': // TODO: use Mixin
return {
// slotSetter
componentName: 'NodeSetter',
props: {
mode: typeName,
},
isRequired,
initialValue: {
type: 'JSSlot',
value: '',
},
};
case 'shape':
case 'exact':
const items = (propType as Shape).value.map((item) => propConfigToFieldConfig(item));
return {
componentName: 'ObjectSetter',
props: {
config: {
items,
extraSetter: typeName === 'shape' ? propTypeToSetter('any') : null,
},
},
isRequired,
initialValue: (field: any) => {
const data: any = {};
items.forEach((item) => {
let initial = item.defaultValue;
if (initial == null && item.setter && typeof item.setter === 'object') {
initial = (item.setter as any).initialValue;
}
data[item.name] = initial ? (typeof initial === 'function' ? initial(field) : initial) : null;
});
return data;
},
};
case 'object':
case 'objectOf':
return {
componentName: 'ObjectSetter',
props: {
config: {
extraSetter: propTypeToSetter(typeName === 'objectOf' ? (propType as ObjectOf).value : 'any'),
},
},
isRequired,
};
case 'array':
case 'arrayOf':
return {
componentName: 'ArraySetter',
props: {
itemSetter: propTypeToSetter(typeName === 'arrayOf' ? (propType as ArrayOf).value : 'any'),
},
isRequired,
initialValue: [],
};
case 'func':
return {
componentName: 'FunctionSetter',
isRequired,
initialValue: {
type: 'JSFunction',
value: 'function(){}',
},
};
case 'oneOfType':
return {
componentName: 'MixinSetter',
props: {
// TODO:
// setters: (propType as OneOfType).value.map(item => propTypeToSetter(item)),
},
isRequired,
};
}
return {
componentName: 'MixinSetter',
isRequired,
};
}
const EVENT_RE = /^on[A-Z][\w]*$/;
export default function(metadata: TransformedComponentMetadata): TransformedComponentMetadata {
const { configure } = metadata;
if (configure.props) {
return metadata;
}
if (!metadata.props) {
return {
...metadata,
configure: {
...configure,
props: [],
},
};
}
const { component = {}, events = {}, styles = {} } = configure;
const supportedEvents: any[] | null = (events as any).supportedEvents ? null : [];
const props: FieldConfig[] = [];
metadata.props.forEach((prop) => {
const { name, propType, description } = prop;
if (
name === 'children' &&
(component.isContainer || propType === 'node' || propType === 'element' || propType === 'any')
) {
if (component.isContainer !== false) {
component.isContainer = true;
return;
}
}
if (EVENT_RE.test(name) && (propType === 'func' || propType === 'any')) {
if (supportedEvents) {
supportedEvents.push({
name,
description,
});
(events as any).supportedEvents = supportedEvents;
}
return;
}
if (name === 'className' && (propType === 'string' || propType === 'any')) {
if ((styles as any).supportClassName == null) {
(styles as any).supportClassName = true;
}
return;
}
if (name === 'style' && (propType === 'object' || propType === 'any')) {
if ((styles as any).supportInlineStyle == null) {
(styles as any).supportInlineStyle = true;
}
return;
}
props.push(propConfigToFieldConfig(prop));
});
return {
...metadata,
configure: {
...configure,
props,
events,
styles,
component,
},
};
}

View File

@ -1,9 +0,0 @@
import { registerMetadataTransducer } from '@ali/lowcode-globals';
import parseProps from './parse-props';
import addonCombine from './addon-combine';
// parseProps
registerMetadataTransducer(parseProps, 10, 'parse-props');
// addon/platform custom
registerMetadataTransducer(addonCombine, 11, 'combine-props');

View File

@ -1,41 +0,0 @@
function getHotterFromSetter(setter) {
return setter && (setter.Hotter || (setter.type && setter.type.Hotter)) || []; // eslint-disable-line
}
function getTransducerFromSetter(setter) {
return setter && (
setter.transducer || setter.Transducer
|| (setter.type && (setter.type.transducer || setter.type.Transducer))
) || null; // eslint-disable-line
}
function combineTransducer(transducer, arr, context) {
if (!transducer && Array.isArray(arr)) {
const [toHot, toNative] = arr;
transducer = { toHot, toNative };
}
return {
toHot: (transducer && transducer.toHot || (x => x)).bind(context), // eslint-disable-line
toNative: (transducer && transducer.toNative || (x => x)).bind(context), // eslint-disable-line
};
}
export class Transducer {
constructor(context, config) {
this.setterTransducer = combineTransducer(
getTransducerFromSetter(config.setter),
getHotterFromSetter(config.setter),
context,
);
this.context = context;
}
toHot(data) {
return this.setterTransducer.toHot(data);
}
toNative(data) {
return this.setterTransducer.toNative(data);
}
}

View File

@ -1,170 +0,0 @@
/*
* 基础的 DPL 定义使用了 kuma base 的定义,参考:
* https://github.com/uxcore/kuma-base/tree/master/variables
*/
/**
* ===========================================================
* ==================== Font Family ==========================
* ===========================================================
*/
/*
* @font-family: "STHeiti", "Microsoft Yahei", "Lucida Grande", "Lucida Sans Unicode", Helvetica, Arial, Verdana, sans-serif;
*/
@font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Helvetica, Arial, sans-serif;
@font-family-code: Monaco, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', Helvetica, Arial, sans-serif;
/**
* ===========================================================
* ===================== Color DPL ===========================
* ===========================================================
*/
@brand-color-1: rgba(0, 108, 255, 1);
@brand-color-2: rgba(25, 122, 255, 1);
@brand-color-3: rgba(0, 96, 229, 1);
@brand-color-1-3: rgba(0, 108, 255, 0.6);
@brand-color-1-4: rgba(0, 108, 255, 0.4);
@brand-color-1-5: rgba(0, 108, 255, 0.3);
@brand-color-1-6: rgba(0, 108, 255, 0.2);
@brand-color-1-7: rgba(0, 108, 255, 0.1);
@brand-color: @brand-color-1;
@white-alpha-1: rgb(255, 255, 255); // W-1
@white-alpha-2: rgba(255, 255, 255, 0.8); // W-2 A80
@white-alpha-3: rgba(255, 255, 255, 0.6); // W-3 A60
@white-alpha-4: rgba(255, 255, 255, 0.4); // W-4 A40
@white-alpha-5: rgba(255, 255, 255, 0.3); // W-5 A30
@white-alpha-6: rgba(255, 255, 255, 0.2); // W-6 A20
@white-alpha-7: rgba(255, 255, 255, 0.1); // W-7 A10
@white-alpha-8: rgba(255, 255, 255, 0.06); // W-8 A6
@dark-alpha-1: rgba(0, 0, 0, 1); // D-1 A100
@dark-alpha-2: rgba(0, 0, 0, 0.8); // D-2 A80
@dark-alpha-3: rgba(0, 0, 0, 0.6); // D-3 A60
@dark-alpha-4: rgba(0, 0, 0, 0.4); // D-4 A40
@dark-alpha-5: rgba(0, 0, 0, 0.3); // D-5 A30
@dark-alpha-6: rgba(0, 0, 0, 0.2); // D-6 A20
@dark-alpha-7: rgba(0, 0, 0, 0.1); // D-7 A10
@dark-alpha-8: rgba(0, 0, 0, 0.06); // D-8 A6
@dark-alpha-9: rgba(0, 0, 0, 0.04); // D-9 A4
@normal-alpha-1: rgba(31, 56, 88, 1); // N-1 A100
@normal-alpha-2: rgba(31, 56, 88, 0.8); // N-2 A80
@normal-alpha-3: rgba(31, 56, 88, 0.6); // N-3 A60
@normal-alpha-4: rgba(31, 56, 88, 0.4); // N-4 A40
@normal-alpha-5: rgba(31, 56, 88, 0.3); // N-5 A30
@normal-alpha-6: rgba(31, 56, 88, 0.2); // N-6 A20
@normal-alpha-7: rgba(31, 56, 88, 0.1); // N-7 A10
@normal-alpha-8: rgba(31, 56, 88, 0.06); // N-8 A6
@normal-alpha-9: rgba(31, 56, 88, 0.04); // N-9 A4
@normal-3: #77879c;
@normal-4: #a3aebd;
@normal-5: #bac3cc;
@normal-6: #d1d7de;
@gray-dark: #333; // N2_4
@gray: #666; // N2_3
@gray-light: #999; // N2_2
@gray-lighter: #ccc; // N2_1
@brand-secondary: #2c2f33; // B2_3
// 补色
@brand-complement: #00b3e8; // B3_1
// 复合
@brand-comosite: #00c587; // B3_2
// 浓度
@brand-deep: #73461d; // B3_3
// F1-1
@brand-danger: rgb(240, 70, 49);
// F1-2 (10% white)
@brand-danger-hover: rgba(240, 70, 49, 0.9);
// F1-3 (5% black)
@brand-danger-focus: rgba(240, 70, 49, 0.95);
// F2-1
@brand-warning: rgb(250, 189, 14);
// F3-1
@brand-success: rgb(102, 188, 92);
// F4-1
@brand-link: rgb(102, 188, 92);
// F4-2
@brand-link-hover: #2e76a6;
// F1-1-7 A10
@brand-danger-alpha-7: rgba(240, 70, 49, 0.9);
// F1-1-8 A6
@brand-danger-alpha-8: rgba(240, 70, 49, 0.8);
// F2-1-2 A80
@brand-warning-alpha-2: rgba(250, 189, 14, 0.8);
// F2-1-7 A10
@brand-warning-alpha-7: rgba(250, 189, 14, 0.9);
// F3-1-2 A80
@brand-success-alpha-2: rgba(102, 188, 92, 0.8);
// F3-1-7 A10
@brand-success-alpha-7: rgba(102, 188, 92, 0.9);
// F4-1-7 A10
@brand-link-alpha-7: rgba(102, 188, 92, 0.9);
// 文本色
@text-primary-color: @dark-alpha-3;
@text-secondary-color: @normal-alpha-3;
@text-thirdary-color: @dark-alpha-4;
@text-disabled-color: @normal-alpha-5;
@text-helper-color: @dark-alpha-4;
@text-danger-color: @brand-danger;
@text-ali-color: #ec6c00;
/**
* ===========================================================
* =================== Shadow Box ============================
* ===========================================================
*/
@box-shadow-1: 0 1px 4px 0 rgba(31, 56, 88, 0.15); // 1 级阴影,物体由原来存在于底面的物体展开,物体和底面关联紧密
@box-shadow-2: 0 2px 10px 0 rgba(31, 56, 88, 0.15); // 2 级阴影hover状态物体层级较高
@box-shadow-3: 0 4px 15px 0 rgba(31, 56, 88, 0.15); // 3 级阴影,当物体层级高于所有界面元素,弹窗用
/**
* ===========================================================
* ================= FontSize of Level =======================
* ===========================================================
*/
@fontSize-1: 26px;
@fontSize-2: 20px;
@fontSize-3: 16px;
@fontSize-4: 14px;
@fontSize-5: 12px;
@fontLineHeight-1: 38px;
@fontLineHeight-2: 30px;
@fontLineHeight-3: 26px;
@fontLineHeight-4: 24px;
@fontLineHeight-5: 20px;
/**
* ===========================================================
* ================= FontSize of Level =======================
* ===========================================================
*/
@global-border-radius: 3px;
@input-border-radius: 3px;
@popup-border-radius: 6px;
/**
* ===========================================================
* ===================== Transistion =========================
* ===========================================================
*/
@transition-duration: 0.3s;
@transition-ease: cubic-bezier(0.23, 1, 0.32, 1);
@transition-delay: 0s;

View File

@ -1,9 +1,8 @@
import { Component, isValidElement, ReactElement, ReactNode } from 'react';
import { Tab, Search, Input, Button } from '@alifd/next';
import Editor from '@ali/lowcode-editor-core';
import { Editor } from '@ali/lowcode-editor-core';
import { js_beautify, css_beautify } from 'js-beautify';
import MonacoEditor from 'react-monaco-editor';
import Panel from '../../vision-polyfill/src/skeleton/widget/panel';
// import lolizer from './sorceEditorPlugin',
@ -47,12 +46,12 @@ interface FunctionEventParam {
export default class SourceEditor extends Component<{
editor: Editor;
panel?: Panel
panel?: any
}> {
private monocoEditer: Object;
private editorCmd: Object;
private monocoEditer: any;
private editorCmd: any;
state = {
state: any = {
isShow: false,
tabKey: TAB_KEY.JS_TAB,
};
@ -117,18 +116,13 @@ export default class SourceEditor extends Component<{
}
openPluginPannel = () => {
const { editor, panel } = this.props;
// 判断面板是否处于激活状态
if (!editor.leftNav || editor.leftNav != 'sourceEditor') {
// 打开面板
editor.emit('leftNav.change', 'sourceEditor');
}
const { panel } = this.props;
if (panel) {
panel.show();
}
}
callEditorEvent = (eventName, params) => {
callEditorEvent = (eventName: any, params: any) => {
if (!this.monocoEditer) {
this.editorCmd = {
eventName,
@ -146,7 +140,7 @@ export default class SourceEditor extends Component<{
};
initCode = (schema) => {
initCode = (schema: any) => {
let jsCode = js_beautify(transfrom.schema2Code(schema), { indent_size: 2, indent_empty_lines: true });
let css;
@ -167,13 +161,13 @@ export default class SourceEditor extends Component<{
*/
addFunction(params: FunctionEventParam) {
const count = this.monocoEditer.getModel().getLineCount() || 0;
const range = new monaco.Range(count, 1, count, 1);
const range = new (window as any).monaco.Range(count, 1, count, 1);
const functionCode = transfrom.getNewFunctionCode(params.functionName);
this.monocoEditer.executeEdits('log-source', [
{ identifier: 'event_id', range: range, text: functionCode, forceMoveMarkers: true },
]);
setTimeout(() => {
let newPosition = new monaco.Position(count + 1, 2);
let newPosition = new (window as any).monaco.Position(count + 1, 2);
this.monocoEditer.setPosition(newPosition);
this.monocoEditer.focus();
}, 100);
@ -204,7 +198,7 @@ export default class SourceEditor extends Component<{
}
}
editorDidMount = (editor, monaco) => {
editorDidMount = (editor: any, monaco: any) => {
console.log('editorDidMount', editor);
this.monocoEditer = editor;
@ -247,13 +241,13 @@ export default class SourceEditor extends Component<{
// });
};
onTabChange = (key) => {
onTabChange = (key: any) => {
this.setState({
selectTab: key,
});
};
updateCode = (newCode) => {
updateCode = (newCode: any) => {
const { selectTab } = this.state;
if (selectTab === TAB_KEY.JS_TAB) {
this.setState({
@ -283,7 +277,7 @@ export default class SourceEditor extends Component<{
{isShow && (
<MonacoEditor
value={selectTab == TAB_KEY.JS_TAB ? jsCode : css}
{...defaultEditorOption}
{...defaultEditorOption as any}
{...{ language: selectTab == TAB_KEY.JS_TAB ? 'javascript' : 'css' }}
onChange={(newCode) => this.updateCode(newCode)}
editorDidMount={this.editorDidMount}

View File

@ -0,0 +1 @@
declare module "js-beautify";

View File

@ -22,6 +22,8 @@
"@ali/lowcode-designer": "^0.9.3",
"@ali/lowcode-editor-core": "^0.8.6",
"@ali/lowcode-editor-skeleton": "^0.8.7",
"@ali/lowcode-types": "^0.8.0",
"@ali/lowcode-utils": "^0.8.0",
"react": "^16.8.1",
"react-dom": "^16.8.1"
},

View File

@ -1,10 +1,11 @@
import React, { PureComponent } from 'react';
import './index.scss';
import Editor, { PluginProps } from '@ali/lowcode-editor-core';
import { Editor } from '@ali/lowcode-editor-core';
import { TopIcon } from '@ali/lowcode-editor-skeleton';
import { Designer } from '@ali/lowcode-designer';
import { PluginProps } from '@ali/lowcode-types';
export interface IProps {
export interface IProps extends PluginProps {
editor: Editor;
logo?: string;
}
@ -14,10 +15,7 @@ export interface IState {
redoEnable: boolean;
}
export default class UndoRedo extends PureComponent<
IProps & PluginProps,
IState
> {
export default class UndoRedo extends PureComponent<IProps, IState> {
public static display = 'LowcodeUndoRedo';
private history: any;
@ -80,18 +78,8 @@ export default class UndoRedo extends PureComponent<
const { undoEnable, redoEnable } = this.state;
return (
<div className="lowcode-plugin-undo-redo">
<TopIcon
icon="houtui"
title="后退"
disabled={!undoEnable}
onClick={this.handleUndoClick}
/>
<TopIcon
icon="qianjin"
title="前进"
disabled={!redoEnable}
onClick={this.handleRedoClick}
/>
<TopIcon icon="houtui" title="后退" disabled={!undoEnable} onClick={this.handleUndoClick} />
<TopIcon icon="qianjin" title="前进" disabled={!redoEnable} onClick={this.handleRedoClick} />
</div>
);
}

View File

@ -15,7 +15,8 @@
},
"dependencies": {
"@ali/lowcode-editor-core": "^0.8.6",
"@ali/lowcode-globals": "^0.9.3",
"@ali/lowcode-utils": "^0.8.0",
"@ali/lowcode-types": "^0.8.0",
"react": "^16.8.1",
"react-dom": "^16.8.1"
},

View File

@ -1,4 +1,4 @@
import { SVGIcon, IconProps } from '@ali/lowcode-globals';
import { SVGIcon, IconProps } from '@ali/lowcode-utils';
export function IconEn(props: IconProps) {
return (

View File

@ -1,4 +1,4 @@
import { SVGIcon, IconProps } from '@ali/lowcode-globals';
import { SVGIcon, IconProps } from '@ali/lowcode-utils';
export function IconZh(props: IconProps) {
return (

View File

@ -1,4 +1,4 @@
import { createIntl } from '@ali/lowcode-globals';
import { createIntl } from '@ali/lowcode-editor-core';
import en_US from './en-US.json';
import zh_CN from './zh-CN.json';

View File

@ -14,7 +14,8 @@
},
"dependencies": {
"@ali/lowcode-designer": "^0.9.3",
"@ali/lowcode-globals": "^0.9.3",
"@ali/lowcode-types": "^0.8.0",
"@ali/lowcode-utils": "^0.8.0",
"@ali/lowcode-react-renderer": "^0.8.4",
"@recore/obx": "^1.0.8",
"@recore/obx-react": "^1.0.7",

View File

@ -30,7 +30,7 @@ export const DateYearSetter = DatePicker.YearPicker;
export const DateMonthSetter = DatePicker.MonthPicker;
export const DateRangeSetter = DatePicker.RangePicker;
export { ExpressionSetter, MixinSetter, EventsSetter }
export { ExpressionSetter, EventsSetter }
// todo:
export const ClassNameSetter = () => {

View File

@ -14,21 +14,14 @@
"test:snapshot": "ava --update-snapshots"
},
"dependencies": {
"@alifd/next": "^1.19.16",
"classnames": "^2.2.6",
"monaco-editor": "^0.20.0",
"react": "^16",
"react-dom": "^16.7.0"
"power-di": "^2.2.4"
},
"devDependencies": {
"@alib/build-scripts": "^0.1.18",
"@types/classnames": "^2.2.7",
"@types/node": "^13.7.1",
"@types/react": "^16",
"@types/react-dom": "^16",
"build-plugin-component": "^0.2.10",
"build-plugin-fusion": "^0.1.0",
"build-plugin-moment-locales": "^0.1.0"
"build-plugin-component": "^0.2.10"
},
"ava": {
"compileEnhancements": false,

View File

@ -1,8 +1,15 @@
import { isReactComponent } from '@ali/lowcode-utils';
import { ComponentType, ReactElement, isValidElement } from 'react';
import { ComponentClass, Component, ComponentType, ReactElement, isValidElement } from 'react';
import { TitleContent } from './title';
import { SettingTarget } from './setting-target';
function isReactClass(obj: any): obj is ComponentClass<any> {
return obj && obj.prototype && (obj.prototype.isReactComponent || obj.prototype instanceof Component);
}
function isReactComponent(obj: any): obj is ComponentType<any> {
return obj && (isReactClass(obj) || typeof obj === 'function');
}
export type CustomView = ReactElement | ComponentType<any>;
export type DynamicProps = (target: SettingTarget) => object;

1
packages/utils/README.md Normal file
View File

@ -0,0 +1 @@
公用函数集合

View File

@ -0,0 +1,5 @@
{
"plugins": [
"build-plugin-component"
]
}

View File

@ -0,0 +1,40 @@
{
"name": "@ali/lowcode-utils",
"version": "0.8.0",
"description": "Utils for Ali lowCode engine",
"files": [
"es",
"lib"
],
"main": "lib/index.js",
"module": "es/index.js",
"scripts": {
"build": "build-scripts build --skip-demo",
"test": "ava",
"test:snapshot": "ava --update-snapshots"
},
"dependencies": {
"react": "^16",
"@alifd/next": "^1.19.16",
"@ali/lowcode-utils": "^0.8.0"
},
"devDependencies": {
"@alib/build-scripts": "^0.1.18",
"@types/node": "^13.7.1",
"@types/react": "^16",
"build-plugin-component": "^0.2.10"
},
"ava": {
"compileEnhancements": false,
"snapshotDir": "test/fixtures/__snapshots__",
"extensions": [
"ts"
],
"require": [
"ts-node/register"
]
},
"publishConfig": {
"registry": "https://registry.npm.alibaba-inc.com"
}
}

View File

@ -1,12 +1,11 @@
import { ComponentType, ReactElement } from 'react';
import { ComponentMetadata, FieldConfig, InitialItem } from '@ali/lowcode-types';
import {
ComponentMetadata,
uniqueId,
ComponentMeta,
addBuiltinComponentAction,
isComponentMeta,
registerMetadataTransducer,
FieldConfig,
InitialItem,
} from '@ali/lowcode-globals';
import { ComponentMeta, addBuiltinComponentAction, isComponentMeta } from '@ali/lowcode-designer';
} from '@ali/lowcode-designer';
import {
OldPropConfig,
OldPrototypeConfig,
@ -16,6 +15,7 @@ import {
upgradeConfigure,
} from './upgrade-metadata';
import { designer } from '../editor';
import { uniqueId } from '@ali/lowcode-utils';
const GlobalPropsConfigure: Array<{ position: string; initials?: InitialItem[]; config: FieldConfig }> = [];
const Overrides: {

View File

@ -1,6 +1,6 @@
import { ReactElement, ComponentType } from 'react';
import { EventEmitter } from 'events';
import { registerSetter } from '@ali/lowcode-globals';
import { registerSetter } from '@ali/lowcode-editor-core';
import Bundle from './bundle';
export class Trunk {

View File

@ -1,10 +1,11 @@
import { ComponentType, ReactElement, isValidElement, ComponentClass } from 'react';
import { isI18nData, SettingTarget, InitialItem, isPlainObject, isJSSlot, isJSExpression } from '@ali/lowcode-globals';
import { isPlainObject } from '@ali/lowcode-utils';
import { isI18nData, SettingTarget, InitialItem, isJSSlot, isJSExpression } from '@ali/lowcode-types';
type Field = SettingTarget;
export enum DISPLAY_TYPE {
NONE = 'none', // => condition'plain'
NONE = 'none', // => condition'plain'
PLAIN = 'plain',
INLINE = 'inline',
BLOCK = 'block',
@ -35,7 +36,7 @@ export interface OldPropConfig {
};
defaultValue?: any; // => extraProps.defaultValue
initialValue?: any | ((value: any, defaultValue: any) => any); // => initials.initialValue
initial?: (value: any, defaultValue: any) => any // => initials.initialValue
initial?: (value: any, defaultValue: any) => any; // => initials.initialValue
display?: DISPLAY_TYPE; // => fieldExtraProps
fieldStyle?: DISPLAY_TYPE; // => fieldExtraProps
@ -71,12 +72,12 @@ export interface OldPropConfig {
mutator?( // => setValue
this: Field,
value: any,
/*
hotValue: any, // => x
hotValue: any,
): /*
preValue: any, // => x
preHotValue: any, // => x
*/
): void;
void;
/**
* other values' change will trigger sync function here
*/
@ -218,13 +219,13 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
newConfig.title = {
label: title,
tip: tip.content,
docUrl: tip.url
docUrl: tip.url,
};
} else {
newConfig.title = {
...(title as any),
tip: tip.content,
docUrl: tip.url
docUrl: tip.url,
};
}
}
@ -260,7 +261,9 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
if (ignore != null || disabled != null) {
// FIXME! addFilter
extraProps.virtual = (field: Field) => {
if (isDisabled(field)) { return true; }
if (isDisabled(field)) {
return true;
}
if (typeof ignore === 'function') {
return ignore.call(field, field.getValue()) === true;
@ -302,7 +305,7 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
}
return currentValue == null ? defaults : currentValue;
}
},
});
if (sync) {
@ -311,11 +314,11 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
if (value !== undefined) {
field.setValue(value);
}
}
};
}
if (mutator && !slotName) {
extraProps.setValue = (field: Field, value: any) => {
mutator.call(field, value);
mutator.call(field, value, value);
};
}
@ -324,18 +327,20 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
if (!newConfig.title && slotTitle) {
newConfig.title = slotTitle;
}
const setters: any[] = [{
componentName: 'SlotSetter',
initialValue: (field: any, value: any) => {
if (isJSSlot(value)) {
return value;
}
return {
type: 'JSSlot',
value: value == null ? initialChildren : value
};
const setters: any[] = [
{
componentName: 'SlotSetter',
initialValue: (field: any, value: any) => {
if (isJSSlot(value)) {
return value;
}
return {
type: 'JSSlot',
value: value == null ? initialChildren : value,
};
},
},
}];
];
if (allowTextInput !== false) {
setters.unshift('StringSetter');
// FIXME: use I18nSetter
@ -351,19 +356,21 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
let primarySetter: any;
if (type === 'composite') {
const initials: InitialItem[] = [];
const objItems = items ? upgradeConfigure(items, (item) => {
initials.push(item);
}) : [];
const objItems = items
? upgradeConfigure(items, (item) => {
initials.push(item);
})
: [];
const initial = (target: SettingTarget, value?: any) => {
// TODO:
const defaults = extraProps.defaultValue;
const data: any = {};
initials.forEach(item => {
initials.forEach((item) => {
// FIXME! Target may be a wrong
data[item.name] = item.initial(target, isPlainObject(value) ? value[item.name] : null);
});
return data;
}
};
addInitial({
name,
initial,
@ -385,9 +392,11 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
primarySetter = setter.map(({ setter, condition }) => {
return {
componentName: setter,
condition: condition ? (field: Field) => {
return condition.call(field, field.getValue());
} : null,
condition: condition
? (field: Field) => {
return condition.call(field, field.getValue());
}
: null,
};
});
} else {
@ -396,7 +405,9 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
}
if (supportVariable) {
if (primarySetter) {
const setters = Array.isArray(primarySetter) ? primarySetter.concat('ExpressionSetter') : [primarySetter, 'ExpressionSetter'];
const setters = Array.isArray(primarySetter)
? primarySetter.concat('ExpressionSetter')
: [primarySetter, 'ExpressionSetter'];
primarySetter = {
componentName: 'MixedSetter',
props: {
@ -405,8 +416,8 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
if (useVariableChange) {
useVariableChange.call(field, { isUseVariable: name === 'ExpressionSetter' });
}
}
}
},
},
};
} else {
primarySetter = 'ExpressionSetter';
@ -447,7 +458,7 @@ export function upgradeActions(actions?: Array<ComponentType<any> | ReactElement
return actions.map((content) => {
const type: any = isValidElement(content) ? content.type : content;
if (typeof content === 'function') {
const fn = content as (() => ReactElement);
const fn = content as () => ReactElement;
content = (({ node }: any) => {
fn.call(node);
}) as any;
@ -457,7 +468,7 @@ export function upgradeActions(actions?: Array<ComponentType<any> | ReactElement
content,
important: true,
};
})
});
}
/**
@ -492,10 +503,11 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
canDroping,
// hooks
canDraging, canDragging, // handleDragging
canDraging,
canDragging, // handleDragging
// events
didDropOut, // onNodeRemove
didDropIn, // onNodeAdd
didDropIn, // onNodeAdd
subtreeModified, // onSubtreeModified
canResizing, // resizing
@ -504,7 +516,6 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
onResizeEnd, // onResizeEnd
} = oldConfig;
const meta: any = {
componentName,
title,
@ -566,7 +577,7 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
experimental.context = context;
}
if (snippets) {
experimental.snippets = snippets.map(data => {
experimental.snippets = snippets.map((data) => {
const { schema = {} } = data;
if (!schema.children && initialChildren && typeof initialChildren !== 'function') {
schema.children = initialChildren;
@ -596,9 +607,12 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
}
}
if (initialChildren) {
experimental.initialChildren = typeof initialChildren === 'function' ? (field: Field) => {
return initialChildren.call(field, (field as any).props);
} : initialChildren;
experimental.initialChildren =
typeof initialChildren === 'function'
? (field: Field) => {
return initialChildren.call(field, (field as any).props);
}
: initialChildren;
}
if (view) {
experimental.view = view;
@ -644,21 +658,21 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
// todo: what is trigger?
const { trigger, deltaX, deltaY } = e;
onResize(e, trigger, currentNode, deltaX, deltaY);
}
};
}
if (onResizeStart) {
callbacks.onResizeStart = (e: any, currentNode: any) => {
// todo: what is trigger?
const { trigger } = e;
onResizeStart(e, trigger, currentNode);
}
};
}
if (onResizeEnd) {
callbacks.onResizeEnd = (e: any, currentNode: any) => {
// todo: what is trigger?
const { trigger } = e;
onResizeEnd(e, trigger, currentNode);
}
};
}
experimental.callbacks = callbacks;
@ -675,5 +689,3 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
meta.experimental = experimental;
return meta;
}

View File

@ -6,7 +6,7 @@ import getTrunkPane from '@ali/ve-trunk-pane';
import EventBindDialog from '@ali/lowcode-plugin-event-bind-dialog';
import loadUrls from './loader';
import { upgradeAssetsBundle } from './upgrade-assets';
import { isCSSUrl } from '@ali/lowcode-globals';
import { isCSSUrl } from '@ali/lowcode-utils';
const { editor, skeleton } = Engine;

View File

@ -1,5 +1,5 @@
import { designer } from './editor';
import { DragObjectType, isNode, TransformStage } from '@ali/lowcode-designer';
import { DragObjectType, isNode } from '@ali/lowcode-designer';
const dragon = designer.dragon;
const DragEngine = {

View File

@ -1,11 +1,11 @@
import { globalContext, isPlainObject, isJSBlock } from '@ali/lowcode-globals';
import Editor from '@ali/lowcode-editor-core';
import { isJSBlock } from '@ali/lowcode-types';
import { isPlainObject } from '@ali/lowcode-utils';
import { globalContext, Editor } from '@ali/lowcode-editor-core';
import { Designer, TransformStage } from '@ali/lowcode-designer';
import { registerSetters } from '@ali/lowcode-setters';
import Outline from '@ali/lowcode-plugin-outline-pane';
import SettingsPane from '@ali/lowcode-plugin-settings-pane';
import DesignerPlugin from '@ali/lowcode-plugin-designer';
import { Skeleton } from './skeleton/skeleton';
import { Skeleton, SettingsPrimaryPane } from '@ali/lowcode-editor-skeleton';
import Preview from '@ali/lowcode-plugin-sample-preview';
@ -78,7 +78,7 @@ skeleton.add({
area: 'rightArea',
name: 'settingsPane',
type: 'Panel',
content: SettingsPane,
content: SettingsPrimaryPane,
});
skeleton.add({
area: 'leftArea',

View File

@ -0,0 +1,3 @@
declare module "@ali/visualengine";
declare module "@ali/visualengine-utils";
declare module "@ali/ve-trunk-pane"

View File

@ -1,5 +1,5 @@
import { designer } from './editor';
import { RootSchema } from '@ali/lowcode-globals';
import { RootSchema } from '@ali/lowcode-types';
import { DocumentModel } from '@ali/lowcode-designer';
const { project } = designer;

View File

@ -1,6 +1,6 @@
import { skeleton, editor } from './editor';
import { ReactElement } from 'react';
import { IWidgetBaseConfig } from './skeleton/types';
import { IWidgetBaseConfig } from '@ali/lowcode-editor-skeleton';
export interface IContentItemConfig {
title: string;

View File

@ -4,7 +4,7 @@ import { fromJS, Iterable, Map as IMMap } from 'immutable';
import logger from '@ali/vu-logger';
import { uniqueId, cloneDeep, isDataEqual, combineInitial, Transducer } from '@ali/ve-utils';
import I18nUtil from '@ali/ve-i18n-util';
import { getSetter } from '@ali/lowcode-globals';
import { getSetter } from '@ali/lowcode-editor-core';
import { editor } from './editor';
import { OldPropConfig, DISPLAY_TYPE } from './bundle/upgrade-metadata';
@ -264,7 +264,7 @@ export default class Prop implements IVariableSettable {
}) {
const accessor = this.config.accessor;
if (accessor && (!options || !options.disableAccessor)) {
const value = accessor.call(this, this.value);
const value = accessor.call(this as any, this.value);
if (!disableCache) {
this.value = value;
}
@ -314,7 +314,7 @@ export default class Prop implements IVariableSettable {
const sync = this.config.sync;
if (sync) {
const value = sync.call(this, this.getValue(true));
const value = sync.call(this as any, this.getValue(true));
if (value !== undefined) {
this.setValue(value);
}
@ -377,7 +377,7 @@ export default class Prop implements IVariableSettable {
this.emitter.emit('ve.prop.useVariableChange', { isUseVariable: flag });
if (this.config.useVariableChange) {
this.config.useVariableChange.call(this, { isUseVariable: flag });
this.config.useVariableChange.call(this as any, { isUseVariable: flag });
}
}
@ -418,7 +418,7 @@ export default class Prop implements IVariableSettable {
}
if (mutator && !extraOptions.disableMutator) {
mutator.call(this, this.value);
mutator.call(this as any, this.value);
}
if (this.modify(force)) {
@ -485,7 +485,7 @@ export default class Prop implements IVariableSettable {
if (!options || !options.disableMutator) {
const mutator = this.config.mutator;
if (mutator) {
mutator.call(this, value);
mutator.call(this as any, value);
}
}
@ -512,7 +512,7 @@ export default class Prop implements IVariableSettable {
let hidden = this.config.hidden;
if (typeof hidden === 'function') {
hidden = hidden.call(this, this.getValue());
hidden = hidden.call(this as any, this.getValue());
}
return hidden === true;
}
@ -520,7 +520,7 @@ export default class Prop implements IVariableSettable {
public isDisabled() {
let disabled = this.config.disabled;
if (typeof disabled === 'function') {
disabled = disabled.call(this, this.getValue());
disabled = disabled.call(this as any, this.getValue());
}
return disabled === true;
}
@ -530,7 +530,7 @@ export default class Prop implements IVariableSettable {
let ignore = this.config.ignore;
if (typeof ignore === 'function') {
ignore = ignore.call(this, this.getValue());
ignore = ignore.call(this as any, this.getValue());
}
return ignore === true;
}

View File

@ -1,59 +0,0 @@
import { obx, computed } from '@ali/lowcode-globals';
import WidgetContainer from './widget/widget-container';
import { Skeleton } from './skeleton';
import { IWidget } from './widget/widget';
import { IWidgetBaseConfig } from './types';
export default class Area<C extends IWidgetBaseConfig = any, T extends IWidget = IWidget> {
@obx private _visible: boolean = true;
@computed get visible() {
if (this.exclusive) {
return this.container.current != null;
}
return this._visible;
}
get current() {
if (this.exclusive) {
return this.container.current;
}
return null;
}
readonly container: WidgetContainer<T, C>;
constructor(readonly skeleton: Skeleton, readonly name: string, handle: (item: T | C) => T, private exclusive?: boolean, defaultSetCurrent: boolean = false) {
this.container = skeleton.createContainer(name, handle, exclusive, () => this.visible, defaultSetCurrent);
}
@computed isEmpty(): boolean {
return this.container.items.length < 1;
}
add(config: T | C): T {
return this.container.add(config);
}
private lastCurrent: T | null = null;
setVisible(flag: boolean) {
if (this.exclusive) {
const current = this.container.current;
if (flag && !current) {
this.container.active(this.lastCurrent || this.container.getAt(0))
} else if (current) {
this.lastCurrent = current;
this.container.unactive(current);
}
return;
}
this._visible = flag;
}
hide() {
this.setVisible(false);
}
show() {
this.setVisible(true);
}
}

View File

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

View File

@ -1,279 +0,0 @@
import { Component, Fragment } from 'react';
import { Icon, Button, Message } from '@alifd/next';
import { Title, SetterType, FieldConfig, SetterConfig } from '@ali/lowcode-globals';
import { createSettingFieldView } from '../../settings/settings-pane';
import { PopupContext, PopupPipe } from '../../popup';
import Sortable from './sortable';
import './style.less';
import { SettingField } from '@ali/lowcode-designer';
interface ArraySetterState {
items: SettingField[];
itemsMap: Map<string | number, SettingField>;
prevLength: number;
}
interface ArraySetterProps {
value: any[];
field: SettingField;
itemSetter?: SetterType;
columns?: FieldConfig[];
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) {
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({
name: i,
setter: props.itemSetter,
// FIXME:
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,
};
}
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) => {
const item = itemsMap.get(id)!;
item.setKey(index);
return item;
});
this.setState({
items,
});
}
private scrollToLast: boolean = false;
onAdd() {
const { items, itemsMap } = this.state;
const { itemSetter } = this.props;
const initialValue = typeof itemSetter === 'object' ? (itemSetter as any).initialValue : null;
const item = this.props.field.createField({
name: items.length,
setter: itemSetter,
// FIXME:
forceInline: 1,
});
items.push(item);
itemsMap.set(item.id, item);
item.setValue(typeof initialValue === 'function' ? initialValue(item) : initialValue);
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() {
let columns: any = null;
if (this.props.columns) {
columns = this.props.columns.map((column) => <Title key={column.name} title={column.title || (column.name as string)} />);
}
const { items } = this.state;
const scrollToLast = this.scrollToLast;
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">
<Button size="medium" onClick={this.onAdd.bind(this)}>
<Icon type="add" />
<span></span>
</Button>
</div>*/}
{columns && <div className="lc-setter-list-columns">{columns}</div>}
{content}
<Button className="lc-setter-list-add" type="primary" onClick={this.onAdd.bind(this)}>
<Icon type="add" />
<span></span>
</Button>
</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 {
// todo:
// forceInline = 1
// has more actions
}
export default class ArraySetter extends Component<{
value: any[];
field: SettingField;
itemSetter?: SetterType;
mode?: 'popup' | 'list';
forceInline?: boolean;
multiValue?: boolean;
}> {
static contextType = PopupContext;
private pipe: any;
render() {
const { mode, forceInline, ...props } = this.props;
const { field, itemSetter } = props;
let columns: FieldConfig[] | undefined;
if ((itemSetter as SetterConfig)?.componentName === 'ObjectSetter') {
const items: FieldConfig[] = (itemSetter as any).props?.config?.items;
if (items && Array.isArray(items)) {
columns = items.filter((item) => item.isRequired || item.important || (item.setter as any)?.isRequired);
if (columns.length > 4) {
columns = columns.slice(0, 4);
}
}
}
if (mode === 'popup' || forceInline) {
const title = (
<Fragment>
<Title title={field.title} />
</Fragment>
);
if (!this.pipe) {
let width = 360;
if (columns) {
if (columns.length === 3) {
width = 480;
} else if (columns.length > 3) {
width = 600;
}
}
this.pipe = (this.context as PopupPipe).create({ width });
}
this.pipe.send(<TableSetter key={field.id} {...props} columns={columns} />, title);
return (
<Button
type={forceInline ? 'normal' : 'primary'}
onClick={(e) => {
this.pipe.show((e as any).target, field.id);
}}
>
<Icon type="edit" />
{forceInline ? title : '编辑数组'}
</Button>
);
} else {
return <ListSetter {...props} columns={columns?.slice(0, 2)} />;
}
}
}

View File

@ -1,29 +0,0 @@
.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;
}
}

View File

@ -1,220 +0,0 @@
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;

View File

@ -1,103 +0,0 @@
.lc-setter-list {
[draggable] {
cursor: move;
}
color: var(--color-text);
.next-btn {
display: inline-flex;
align-items: center;
line-height: 1 !important;
max-width: 100%;
text-overflow: ellipsis;
}
.lc-setter-list-add {
display: block;
width: 100%;
margin-top: 8px;;
}
.lc-setter-list-columns {
display: flex;
> .lc-title {
flex: 1;
justify-content: center;
}
margin-left: 47px;
margin-right: 28px;
margin-bottom: 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;
&:not(:last-child) {
margin-bottom: 5px;
}
.lc-listitem {
position: relative;
outline: none;
display: flex;
align-items: stretch;
height: 34px;
.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: stretch;
overflow: hidden;
min-width: 0;
text-overflow: ellipsis;
.lc-field {
padding: 0 !important;
display: flex;
align-items: center;
>.lc-field-body {
justify-content: center;
}
}
> * {
width: 100%;
}
.next-btn {
display: block;
width: 100%;
}
}
.lc-listitem-handler {
margin-left: 2px;
display: inline-flex;
align-items: center;
.next-icon-ellipsis {
transform: rotate(90deg);
}
opacity: 0.6;
}
}
}
}

View File

@ -1,184 +0,0 @@
import { Component } from 'react';
import classNames from 'classnames';
import { Icon } from '@alifd/next';
import { Title, TitleContent } from '@ali/lowcode-globals';
import { PopupPipe, PopupContext } from '../popup';
import './index.less';
export interface FieldProps {
className?: string;
title?: TitleContent | null;
defaultDisplay?: 'accordion' | 'inline' | 'block';
collapsed?: boolean;
onExpandChange?: (expandState: boolean) => void;
}
export class Field extends Component<FieldProps> {
state = {
collapsed: this.props.collapsed,
display: this.props.defaultDisplay || 'inline',
};
private toggleExpand = () => {
const { onExpandChange } = this.props;
const collapsed = !this.state.collapsed;
this.setState({
collapsed,
});
onExpandChange && onExpandChange(!collapsed);
};
private body: HTMLDivElement | null = null;
private dispose?: () => void;
private deployBlockTesting() {
if (this.dispose) {
this.dispose();
}
const body = this.body;
if (!body) {
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: true,
attributes: true,
attributeFilter: ['class'],
});
this.dispose = () => observer.disconnect();
}
componentDidMount() {
const { defaultDisplay } = this.props;
if (!defaultDisplay || defaultDisplay === 'inline') {
this.deployBlockTesting();
}
}
componentWillUnmount() {
if (this.dispose) {
this.dispose();
}
}
render() {
const { className, children, title } = this.props;
const { display, collapsed } = this.state;
const isAccordion = display === 'accordion';
return (
<div
className={classNames(`lc-field lc-${display}-field`, className, {
'lc-field-is-collapsed': isAccordion && collapsed,
})}
>
<div className="lc-field-head" onClick={isAccordion ? this.toggleExpand : undefined}>
<div className="lc-field-title">
<Title title={title || ''} />
</div>
{isAccordion && <Icon className="lc-field-icon" type="arrow-up" size="xs" />}
</div>
<div key="body" ref={(shell) => (this.body = shell)} className="lc-field-body">
{children}
</div>
</div>
);
}
}
export interface PopupFieldProps extends FieldProps {
width?: number;
}
export class PopupField extends Component<PopupFieldProps> {
static contextType = PopupContext;
private pipe: any;
static defaultProps: PopupFieldProps = {
width: 300,
};
render() {
const { className, children, title, width } = this.props;
if (!this.pipe) {
this.pipe = (this.context as PopupPipe).create({ width });
}
const titleElement = title && (
<div className="lc-field-title">
<Title title={title} />
</div>
);
this.pipe.send(<div className="lc-field-body">{children}</div>, titleElement);
return (
<div className={classNames('lc-field lc-popup-field', className)}>
{title && (
<div
className="lc-field-head"
onClick={(e) => {
this.pipe.show((e as any).target);
}}
>
<div className="lc-field-title">
<Title title={title} />
</div>
<Icon className="lc-field-icon" type="arrow-left" size="xs" />
</div>
)}
</div>
);
}
}
export interface EntryFieldProps extends FieldProps {
stageName?: string;
}
export class EntryField extends Component<EntryFieldProps> {
render() {
const { stageName, title, className } = this.props;
const classNameList = classNames('engine-setting-field', 'engine-entry-field', className);
const fieldProps: any = {};
if (stageName) {
// 为 stage 切换奠定基础
fieldProps['data-stage-target'] = stageName;
}
const innerElements = [
<span className="engine-field-title" key="field-title">
{title}
</span>,
// renderTip(tip, { propName }),
// <Icons name="arrow" className="engine-field-arrow" size="12px" key="engine-field-arrow-icon" />,
];
return (
<div className={classNameList} {...fieldProps}>
{innerElements}
</div>
);
}
}
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

@ -1,154 +0,0 @@
@import '../variables.less';
@x-gap: 10px;
@y-gap: 8px;
.lc-field {
// head
.lc-field-head {
display: flex;
align-items: center;
justify-content: space-between;
.lc-field-title {
display: flex;
align-items: center;
}
.lc-field-icon {
margin-right: @x-gap;
transform-origin: center;
transition: transform 0.1s;
}
}
&.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 {
display: flex;
align-items: center;
// for top-level style
padding: 8px 10px;
> .lc-field-head {
width: 70px;
margin-right: 1px;
.lc-title-label {
width: 70px;
word-break: break-all;
}
}
> .lc-field-body {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
}
}
&.lc-block-field, &.lc-accordion-field {
display: block;
&:not(:first-child) {
border-top: 1px solid var(--color-line-normal, @dark-alpha-2);
}
> .lc-field-head {
padding-left: @x-gap;
height: 32px;
display: flex;
align-items: center;
font-weight: 500;
background: var(--color-block-background-shallow, rgba(31,56,88,.06));
border-bottom: 1px solid var(--color-line-normal, @dark-alpha-2);
color: var(--color-title, @white-alpha-2);
user-select: none;
}
> .lc-field-body {
padding: @y-gap @x-gap/2;
}
+ .lc-inline-field {
border-top: 1px solid var(--color-line-normal, @dark-alpha-2);
}
}
.lc-setter-actions {
display: flex;
align-items: center;
}
&.lc-block-field {
position: relative;
>.lc-field-body>.lc-block-setter>.lc-setter-actions {
position: absolute;
right: 10px;
top: 0;
height: 32px;
display: flex;
align-items: center;
}
}
&.lc-accordion-field {
// collapsed
&.lc-field-is-collapsed {
> .lc-field-head .lc-field-icon {
transform: rotate(180deg);
}
> .lc-field-body {
display: none;
}
}
// 邻近的保持上下距离
+ .lc-field {
margin-top: @y-gap;
}
}
// 2rd level reset
.lc-field-body {
.lc-inline-field {
padding: @y-gap @x-gap/2 0 @x-gap/2;
&:first-child {
padding-top: 0;
}
+ .lc-accordion-field, +.lc-block-field {
margin-top: @y-gap;
}
}
.lc-field {
border-top: none !important;
}
.lc-accordion-field, .lc-block-field {
> .lc-field-head {
padding-left: @x-gap/2;
background: var(--color-block-background-light);
border-bottom-color: var(--color-line-light);
> .lc-field-icon {
margin-right: @x-gap/2;
}
}
}
// 3rd level field title width should short
.lc-field-body .lc-inline-field {
> .lc-field-head {
width: 50px;
.lc-title-label {
width: 50px;
}
}
}
}
}

View File

@ -1,28 +0,0 @@
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,257 +0,0 @@
import React, { Component, isValidElement } from 'react';
import classNames from 'classnames';
import { Dropdown, Button, Menu } from '@alifd/next';
import {
getSetter,
getSettersMap,
SetterConfig,
computed,
obx,
CustomView,
DynamicProps,
DynamicSetter,
TitleContent,
isSetterConfig,
Title,
createSetterContent,
observer,
isDynamicSetter,
shallowIntl,
EmbedTip,
isI18nData,
} from '@ali/lowcode-globals';
import { IconConvert } from '../../icons/convert';
import './style.less';
import { SettingField } from '@ali/lowcode-designer';
export interface SetterItem {
name: string;
title: TitleContent;
setter: string | DynamicSetter | CustomView;
props?: object | DynamicProps;
condition?: (field: SettingField) => boolean;
initialValue?: any | ((field: SettingField) => any);
list: boolean;
}
function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | DynamicSetter>): SetterItem[] {
if (!setters) {
const normalized: SetterItem[] = [];
getSettersMap().forEach((setter, name) => {
if (name === 'MixedSetter') {
return;
}
normalized.push({
name,
title: setter.title || name,
setter: name,
condition: setter.condition,
initialValue: setter.initialValue,
list: setter.recommend || false,
});
});
return normalized;
}
const names: string[] = [];
function generateName(n: string) {
let idx = 1;
let got = n;
while (names.indexOf(got) > -1) {
got = `${n}:${idx++}`;
}
names.push(got);
return got;
}
return setters.map((setter) => {
const config: any = {
setter,
list: true,
};
if (isSetterConfig(setter)) {
config.setter = setter.componentName;
config.props = setter.props;
config.condition = setter.condition;
config.initialValue = setter.initialValue;
config.title = setter.title;
}
if (typeof config.setter === 'string') {
config.name = config.setter;
names.push(config.name);
const info = getSetter(config.setter);
if (!config.title) {
config.title = info?.title || config.setter;
}
if (!config.condition) {
config.condition = info?.condition;
}
if (!config.initialValue) {
config.initialValue = info?.initialValue;
}
} else {
config.name = generateName((config.setter as any).displayName || (config.setter as any).name || 'CustomSetter');
if (!config.title) {
config.title = config.name;
}
}
return config;
});
}
@observer
export default class MixedSetter extends Component<{
field: SettingField;
setters?: Array<string | SetterConfig | CustomView | DynamicSetter>;
onSetterChange?: (field: SettingField, name: string) => void;
onChange?: (val: any) => void;
value?: any;
className?: string;
}> {
private setters = nomalizeSetters(this.props.setters);
@obx.ref private used?: string;
@computed private getCurrentSetter() {
const { field } = this.props;
let firstMatched: SetterItem | undefined;
for (const setter of this.setters) {
const matched = !setter.condition || setter.condition(field);
if (matched) {
if (setter.name === this.used) {
return setter;
}
if (!firstMatched) {
firstMatched = setter;
}
}
}
return firstMatched;
}
private useSetter = (name: string) => {
if (name === this.used) {
return;
}
const { field, onChange } = this.props;
const setter = this.setters.find((item) => item.name === name);
this.used = name;
if (setter) {
let newValue: any = setter.initialValue;
if (newValue && typeof newValue === 'function') {
newValue = newValue(field);
}
onChange && onChange(newValue);
}
};
private shell: HTMLDivElement | null = null;
private checkIsBlockField() {
if (this.shell) {
const setter = this.shell.firstElementChild;
if (setter && setter.classList.contains('lc-block-setter')) {
this.shell.classList.add('lc-block-setter');
} else {
this.shell.classList.remove('lc-block-setter');
}
}
}
componentDidUpdate() {
this.checkIsBlockField();
}
componentDidMount() {
this.checkIsBlockField();
}
render() {
const { className, field, setters, onSetterChange, ...restProps } = this.props;
const currentSetter = this.getCurrentSetter();
const isTwoType = this.setters.length < 3;
let setterContent: any;
const triggerTitle: any = {
tip: {
type: 'i18n',
'zh-CN': '切换格式',
'en-US': 'Switch Format',
},
icon: <IconConvert size={24} />,
};
if (currentSetter) {
const { setter, title, props } = currentSetter;
let setterProps: any = {};
let setterType: any;
if (isDynamicSetter(setter)) {
setterType = setter.call(field, field);
} else {
setterType = setter;
}
if (props) {
setterProps = props;
if (typeof setterProps === 'function') {
setterProps = setterProps(field);
}
}
setterContent = createSetterContent(setterType, {
...shallowIntl(setterProps),
field,
...restProps,
});
if (title) {
if (typeof title !== 'object' || isI18nData(title) || isValidElement(title)) {
triggerTitle.tip = title;
} else {
triggerTitle.tip = title.tip || title.label;
}
}
} else {
// 未匹配的 null 值,显示 NullValue 空值
// 未匹配的 其它 值,显示 InvalidValue 非法值
if (restProps.value == null) {
setterContent = <span>NullValue</span>;
} else {
setterContent = <span>InvalidValue</span>;
}
}
const usedName = currentSetter?.name || this.used;
let moreBtnNode = (
<Title
title={triggerTitle}
className="lc-switch-trigger"
onClick={
isTwoType
? () => {
if (this.setters[0]?.name === usedName) {
this.useSetter(this.setters[1]?.name);
} else {
this.useSetter(this.setters[0]?.name);
}
}
: undefined
}
/>
);
if (!isTwoType) {
moreBtnNode = (
<Dropdown trigger={moreBtnNode} triggerType="click" align="tr br">
<Menu selectMode="single" hasSelectedIcon={true} selectedKeys={usedName} onItemClick={this.useSetter}>
{this.setters.filter(setter => setter.list || setter.name === usedName).map((setter) => {
return (
<Menu.Item key={setter.name}>
<Title title={setter.title} />
</Menu.Item>
);
})}
</Menu>
</Dropdown>
);
}
return (
<div ref={(shell) => (this.shell = shell)} className={classNames('lc-setter-mixed', className)}>
{setterContent}
<div className="lc-setter-actions">{moreBtnNode}</div>
</div>
);
}
}

View File

@ -1,30 +0,0 @@
.lc-setter-mixed {
flex: 1;
min-width: 0;
margin-right: 26px;
display: block;
position: relative;
>.lc-setter-actions {
position: absolute;
right: -2px;
top: 50%;
transform: translate(100%, -50%);
.lc-switch-trigger {
cursor: pointer;
opacity: 0.6;
&:hover {
opacity: 1;
}
}
}
.next-input,.next-date-picker {
width: 100%;
}
&.lc-block-setter {
position: static;
margin-right: 0;
>.lc-setter-actions {
transform: none;
}
}
}

View File

@ -1,180 +0,0 @@
import { Component, Fragment } from 'react';
import { Icon, Button } from '@alifd/next';
import { Title, SetterType, FieldConfig } from '@ali/lowcode-globals';
import { createSettingFieldView } from '../../settings/settings-pane';
import { PopupContext, PopupPipe } from '../../popup';
import { SettingField } from '@ali/lowcode-designer';
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 {...props} />;
}
}
}
interface ObjectSetterConfig {
items?: FieldConfig[];
extraSetter?: SetterType;
}
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.isRequired || conf.important || (conf.setter as any)?.isRequired) {
const item = field.createField({
...conf,
// in column-cell
forceInline: 3,
});
items.push(item);
}
}
}
if (items.length > 0) {
this.items = items;
}
let firstRun: boolean = true;
field.onEffect(() => {
let state: any = {};
if (descriptor) {
if (typeof descriptor === 'function') {
state.descriptor = descriptor(field);
} else {
state.descriptor = field.getPropValue(descriptor);
}
} else {
state.descriptor = field.title;
}
if (firstRun) {
firstRun = false;
this.state = state;
} else {
this.setState(state);
}
});
}
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, config } = 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} field={field} config={config} />, 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, field.id);
}}
>
<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, field.id);
}}
>
<Icon type="edit" />
{title}
</Button>
);
}
}
interface FormSetterProps {
field: SettingField;
config: ObjectSetterConfig;
}
class FormSetter extends Component<FormSetterProps> {
private items: SettingField[];
constructor(props: RowSetterProps) {
super(props);
const { config, field } = props;
this.items = (config.items || []).map((conf) => field.createField(conf));
// TODO: extraConfig for custom fields
}
shouldComponentUpdate() {
return false;
}
render() {
const { field } = this.props;
return (
<div className="lc-setter-object lc-block-setter">
{this.items.map((item, index) => createSettingFieldView(item, field, index))}
</div>
);
}
}

View File

@ -1,31 +0,0 @@
.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;
}
}
}

Some files were not shown because too many files have changed in this diff Show More