mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-04-19 20:08:05 +00:00
optimize structure
This commit is contained in:
parent
c5f95c5316
commit
b486a8419c
@ -1,8 +1,9 @@
|
|||||||
import { Component, Fragment, PureComponent } from 'react';
|
import { Component, Fragment, PureComponent } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { computed, observer, TitleContent, Title } from '@ali/lowcode-globals';
|
import { computed, observer, Title } from '@ali/lowcode-editor-core';
|
||||||
import { SimulatorContext } from '../context';
|
import { SimulatorContext } from '../context';
|
||||||
import { BuiltinSimulatorHost } from '../host';
|
import { BuiltinSimulatorHost } from '../host';
|
||||||
|
import { TitleContent } from '@ali/lowcode-types';
|
||||||
|
|
||||||
export class BorderHoveringInstance extends PureComponent<{
|
export class BorderHoveringInstance extends PureComponent<{
|
||||||
title: TitleContent;
|
title: TitleContent;
|
||||||
|
|||||||
@ -9,19 +9,13 @@ import {
|
|||||||
ComponentType,
|
ComponentType,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {
|
import { observer, computed, Tip } from '@ali/lowcode-editor-core';
|
||||||
observer,
|
import { createIcon, isReactComponent } from '@ali/lowcode-utils';
|
||||||
computed,
|
import { ActionContentObject, isActionContentObject } from '@ali/lowcode-types';
|
||||||
createIcon,
|
|
||||||
EmbedTip,
|
|
||||||
isReactComponent,
|
|
||||||
ActionContentObject,
|
|
||||||
isActionContentObject,
|
|
||||||
} from '@ali/lowcode-globals';
|
|
||||||
import { SimulatorContext } from '../context';
|
import { SimulatorContext } from '../context';
|
||||||
import { BuiltinSimulatorHost } from '../host';
|
import { BuiltinSimulatorHost } from '../host';
|
||||||
import { OffsetObserver } from '../../../designer';
|
import { OffsetObserver } from '../../designer';
|
||||||
import { Node } from '../../../document';
|
import { Node } from '../../document';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class BorderSelectingInstance extends Component<{
|
export class BorderSelectingInstance extends Component<{
|
||||||
@ -94,9 +88,9 @@ class Toolbar extends Component<{ observed: OffsetObserver }> {
|
|||||||
}
|
}
|
||||||
const { node } = observed;
|
const { node } = observed;
|
||||||
const actions: ReactNodeArray = [];
|
const actions: ReactNodeArray = [];
|
||||||
node.componentMeta.availableActions.forEach(action => {
|
node.componentMeta.availableActions.forEach((action) => {
|
||||||
const { important, condition, content, name } = action;
|
const { important, condition, content, name } = action;
|
||||||
if (node.isSlotRoot && (name === 'copy' || name === 'remove')) {
|
if (node.isSlot() && (name === 'copy' || name === 'remove')) {
|
||||||
// FIXME: need this?
|
// FIXME: need this?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -130,7 +124,7 @@ function createAction(content: ReactNode | ComponentType<any> | ActionContentObj
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{icon && createIcon(icon)}
|
{icon && createIcon(icon)}
|
||||||
<EmbedTip>{title}</EmbedTip>
|
<Tip>{title}</Tip>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -167,7 +161,7 @@ export class BorderSelectingForNode extends Component<{ node: Node }> {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Fragment key={node.id}>
|
<Fragment key={node.id}>
|
||||||
{instances.map(instance => {
|
{instances.map((instance) => {
|
||||||
const observed = designer.createOffsetObserver({
|
const observed = designer.createOffsetObserver({
|
||||||
node,
|
node,
|
||||||
instance,
|
instance,
|
||||||
@ -216,7 +210,7 @@ export class BorderSelecting extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{selecting.map(node => (
|
{selecting.map((node) => (
|
||||||
<BorderSelectingForNode key={node.id} node={node} />
|
<BorderSelectingForNode key={node.id} node={node} />
|
||||||
))}
|
))}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import { observer } from '@ali/lowcode-globals';
|
import { observer } from '@ali/lowcode-editor-core';
|
||||||
import { BorderHovering } from './border-hovering';
|
import { BorderHovering } from './border-hovering';
|
||||||
import { SimulatorContext } from '../context';
|
import { SimulatorContext } from '../context';
|
||||||
import { BuiltinSimulatorHost } from '../host';
|
import { BuiltinSimulatorHost } from '../host';
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import { computed, observer } from '@ali/lowcode-globals';
|
import { computed, observer } from '@ali/lowcode-editor-core';
|
||||||
import { SimulatorContext } from '../context';
|
import { SimulatorContext } from '../context';
|
||||||
import { BuiltinSimulatorHost } from '../host';
|
import { BuiltinSimulatorHost } from '../host';
|
||||||
import {
|
import {
|
||||||
@ -10,7 +10,7 @@ import {
|
|||||||
isVertical
|
isVertical
|
||||||
} from '../../designer';
|
} from '../../designer';
|
||||||
import { ISimulatorHost, } from '../../simulator';
|
import { ISimulatorHost, } from '../../simulator';
|
||||||
import {ParentalNode } from '../../document';
|
import { ParentalNode } from '../../document';
|
||||||
import './insertion.less';
|
import './insertion.less';
|
||||||
|
|
||||||
interface InsertionData {
|
interface InsertionData {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// NOTE: 仅用作类型标注,切勿作为实体使用
|
// NOTE: 仅用作类型标注,切勿作为实体使用
|
||||||
import { BuiltinSimulatorHost } from './host';
|
import { BuiltinSimulatorHost } from './host';
|
||||||
import { AssetLevel, AssetLevels, AssetList, isAssetBundle, isAssetItem, AssetType, assetItem } from '@ali/lowcode-globals';
|
import { AssetLevel, AssetLevels, AssetList, isAssetBundle, isAssetItem, AssetType, assetItem } from '@ali/lowcode-utils';
|
||||||
import { isCSSUrl } from '@ali/lowcode-globals';
|
import { isCSSUrl } from '@ali/lowcode-utils';
|
||||||
import { BuiltinSimulatorRenderer } from './renderer';
|
import { BuiltinSimulatorRenderer } from './renderer';
|
||||||
|
|
||||||
export function createSimulator(
|
export function createSimulator(
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import { observer } from '@ali/lowcode-globals';
|
import { observer } from '@ali/lowcode-editor-core';
|
||||||
import { BuiltinSimulatorHost, BuiltinSimulatorProps } from './host';
|
import { BuiltinSimulatorHost, BuiltinSimulatorProps } from './host';
|
||||||
import { DocumentModel } from '../document';
|
import { DocumentModel } from '../document';
|
||||||
import { SimulatorContext } from './context';
|
import { SimulatorContext } from './context';
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { obx, autorun, computed } from '@ali/lowcode-globals';
|
import { obx, autorun, computed, getPublicPath, hotkey } from '@ali/lowcode-editor-core';
|
||||||
import { ISimulatorHost, Component, NodeInstance, ComponentInstance } from '../simulator';
|
import { ISimulatorHost, Component, NodeInstance, ComponentInstance } from '../simulator';
|
||||||
import Viewport from './viewport';
|
import Viewport from './viewport';
|
||||||
import { createSimulator } from './create-simulator';
|
import { createSimulator } from './create-simulator';
|
||||||
import { Node, ParentalNode, DocumentModel, isNode, contains, isRootNode } from '../document';
|
import { Node, ParentalNode, DocumentModel, isNode, contains, isRootNode } from '../document';
|
||||||
import ResourceConsumer from './resource-consumer';
|
import ResourceConsumer from './resource-consumer';
|
||||||
import { AssetLevel, Asset, AssetList, assetBundle, assetItem, AssetType, getPublicPath } from '@ali/lowcode-globals';
|
import { AssetLevel, Asset, AssetList, assetBundle, assetItem, AssetType, isElement } from '@ali/lowcode-utils';
|
||||||
import {
|
import {
|
||||||
DragObjectType,
|
DragObjectType,
|
||||||
isShaken,
|
isShaken,
|
||||||
@ -21,9 +21,8 @@ import {
|
|||||||
Rect,
|
Rect,
|
||||||
CanvasPoint,
|
CanvasPoint,
|
||||||
} from '../designer';
|
} from '../designer';
|
||||||
import { parseProps, parseMetadata } from './utils/parse-metadata';
|
import { parseMetadata } from './utils/parse-metadata';
|
||||||
import { isElement, hotkey } from '@ali/lowcode-globals';
|
import { ComponentMetadata } from '@ali/lowcode-types';
|
||||||
import { ComponentMetadata } from '@ali/lowcode-globals';
|
|
||||||
import { BuiltinSimulatorRenderer } from './renderer';
|
import { BuiltinSimulatorRenderer } from './renderer';
|
||||||
import clipboard from '../designer/clipboard';
|
import clipboard from '../designer/clipboard';
|
||||||
|
|
||||||
@ -967,7 +966,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
|
|||||||
return this.document.checkDropTarget(container, dragObject as any);
|
return this.document.checkDropTarget(container, dragObject as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = container.componentMeta;
|
const meta = (container as Node).componentMeta;
|
||||||
|
|
||||||
// FIXME: get containerInstance for accept logic use
|
// FIXME: get containerInstance for accept logic use
|
||||||
const acceptable: boolean = this.isAcceptable(container);
|
const acceptable: boolean = this.isAcceptable(container);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { autorun, obx } from '@ali/lowcode-globals';
|
import { autorun, obx } from '@ali/lowcode-editor-core';
|
||||||
import { BuiltinSimulatorHost } from './host';
|
import { BuiltinSimulatorHost } from './host';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { BuiltinSimulatorRenderer, isSimulatorRenderer } from './renderer';
|
import { BuiltinSimulatorRenderer, isSimulatorRenderer } from './renderer';
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { isValidElement } from 'react';
|
import { isValidElement } from 'react';
|
||||||
import { isElement } from '@ali/lowcode-globals';
|
import { isElement } from '@ali/lowcode-utils';
|
||||||
import { PropConfig } from '@ali/lowcode-globals';
|
import { PropConfig } from '@ali/lowcode-types';
|
||||||
|
|
||||||
export const primitiveTypes = [
|
export const primitiveTypes = [
|
||||||
'string',
|
'string',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { obx, computed } from '@ali/lowcode-globals';
|
import { obx, computed } from '@ali/lowcode-editor-core';
|
||||||
import { Point, ScrollTarget } from '../designer';
|
import { Point, ScrollTarget } from '../designer';
|
||||||
import { AutoFit, IViewport } from '../simulator';
|
import { AutoFit, IViewport } from '../simulator';
|
||||||
|
|
||||||
|
|||||||
@ -6,11 +6,9 @@ import {
|
|||||||
ComponentAction,
|
ComponentAction,
|
||||||
TitleContent,
|
TitleContent,
|
||||||
TransformedComponentMetadata,
|
TransformedComponentMetadata,
|
||||||
getRegisteredMetadataTransducers,
|
|
||||||
registerMetadataTransducer,
|
|
||||||
computed,
|
|
||||||
NestingFilter,
|
NestingFilter,
|
||||||
} from '@ali/lowcode-globals';
|
} from '@ali/lowcode-types';
|
||||||
|
import { computed } from '@ali/lowcode-editor-core';
|
||||||
import { Node, ParentalNode } from './document';
|
import { Node, ParentalNode } from './document';
|
||||||
import { Designer } from './designer';
|
import { Designer } from './designer';
|
||||||
import { intl } from './locale';
|
import { intl } from './locale';
|
||||||
@ -130,11 +128,14 @@ export class ComponentMeta {
|
|||||||
|
|
||||||
const title = this._transformedMetadata.title;
|
const title = this._transformedMetadata.title;
|
||||||
if (title) {
|
if (title) {
|
||||||
this._title = typeof title === 'string' ? {
|
this._title =
|
||||||
type: 'i18n',
|
typeof title === 'string'
|
||||||
'en-US': this.componentName,
|
? {
|
||||||
'zh-CN': title,
|
type: 'i18n',
|
||||||
} : title;
|
'en-US': this.componentName,
|
||||||
|
'zh-CN': title,
|
||||||
|
}
|
||||||
|
: title;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { configure = {} } = this._transformedMetadata;
|
const { configure = {} } = this._transformedMetadata;
|
||||||
@ -300,7 +301,7 @@ const builtinComponentActions: ComponentAction[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export function removeBuiltinComponentAction(name: string) {
|
export function removeBuiltinComponentAction(name: string) {
|
||||||
const i = builtinComponentActions.findIndex(action => action.name === name);
|
const i = builtinComponentActions.findIndex((action) => action.name === name);
|
||||||
if (i > -1) {
|
if (i > -1) {
|
||||||
builtinComponentActions.splice(i, 1);
|
builtinComponentActions.splice(i, 1);
|
||||||
}
|
}
|
||||||
@ -308,3 +309,33 @@ export function removeBuiltinComponentAction(name: string) {
|
|||||||
export function addBuiltinComponentAction(action: ComponentAction) {
|
export function addBuiltinComponentAction(action: ComponentAction) {
|
||||||
builtinComponentActions.push(action);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { hotkey, isFormEvent } from '@ali/lowcode-globals';
|
import { hotkey } from '@ali/lowcode-editor-core';
|
||||||
|
import { isFormEvent } from '@ali/lowcode-utils';
|
||||||
import { focusing } from './focusing';
|
import { focusing } from './focusing';
|
||||||
import { insertChildren } from '../document';
|
import { insertChildren, TransformStage } from '../document';
|
||||||
import clipboard from './clipboard';
|
import clipboard from './clipboard';
|
||||||
|
|
||||||
// hotkey binding
|
// hotkey binding
|
||||||
@ -52,7 +53,7 @@ hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => {
|
|||||||
if (!selected || selected.length < 1) return;
|
if (!selected || selected.length < 1) return;
|
||||||
|
|
||||||
const componentsMap = {};
|
const componentsMap = {};
|
||||||
const componentsTree = selected.map((item) => item.export(false));
|
const componentsTree = selected.map((item) => item.export(TransformStage.Save));
|
||||||
|
|
||||||
const data = { type: 'nodeSchema', componentsMap, componentsTree };
|
const data = { type: 'nodeSchema', componentsMap, componentsTree };
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { TipContainer } from '@ali/lowcode-globals';
|
|
||||||
import BuiltinDragGhostComponent from './drag-ghost';
|
import BuiltinDragGhostComponent from './drag-ghost';
|
||||||
import { Designer, DesignerProps } from './designer';
|
import { Designer, DesignerProps } from './designer';
|
||||||
import { ProjectView } from '../project';
|
import { ProjectView } from '../project';
|
||||||
|
|||||||
@ -1,16 +1,14 @@
|
|||||||
import { ComponentType } from 'react';
|
import { ComponentType } from 'react';
|
||||||
|
import { obx, computed, autorun } from '@ali/lowcode-editor-core';
|
||||||
import {
|
import {
|
||||||
ProjectSchema,
|
ProjectSchema,
|
||||||
ComponentMetadata,
|
ComponentMetadata,
|
||||||
ComponentAction,
|
ComponentAction,
|
||||||
NpmInfo,
|
NpmInfo,
|
||||||
obx,
|
|
||||||
computed,
|
|
||||||
autorun,
|
|
||||||
IEditor,
|
IEditor,
|
||||||
CompositeObject,
|
CompositeObject,
|
||||||
PropsList,
|
PropsList,
|
||||||
} from '@ali/lowcode-globals';
|
} from '@ali/lowcode-types';
|
||||||
import { Project } from '../project';
|
import { Project } from '../project';
|
||||||
import { Node, DocumentModel, insertChildren, isRootNode, ParentalNode, TransformStage } from '../document';
|
import { Node, DocumentModel, insertChildren, isRootNode, ParentalNode, TransformStage } from '../document';
|
||||||
import { ComponentMeta } from '../component-meta';
|
import { ComponentMeta } from '../component-meta';
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import { observer, obx, Title } from '@ali/lowcode-globals';
|
import { observer, obx, Title } from '@ali/lowcode-editor-core';
|
||||||
import { Designer } from '../designer';
|
import { Designer } from '../designer';
|
||||||
import { DragObject, isDragNodeObject, isDragNodeDataObject } from '../dragon';
|
import { DragObject, isDragNodeObject, isDragNodeDataObject } from '../dragon';
|
||||||
import './ghost.less';
|
import './ghost.less';
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { NodeSchema, obx } from '@ali/lowcode-globals';
|
import { obx } from '@ali/lowcode-editor-core';
|
||||||
|
import { NodeSchema } from '@ali/lowcode-types';
|
||||||
|
import { setNativeSelection, cursor } from '@ali/lowcode-utils';
|
||||||
import { DropLocation } from './location';
|
import { DropLocation } from './location';
|
||||||
import { Node, DocumentModel } from '../document';
|
import { Node, DocumentModel } from '../document';
|
||||||
import { ISimulatorHost, isSimulatorHost } from '../simulator';
|
import { ISimulatorHost, isSimulatorHost } from '../simulator';
|
||||||
import { Designer } from './designer';
|
import { Designer } from './designer';
|
||||||
import { setNativeSelection } from '@ali/lowcode-globals';
|
|
||||||
import { cursor } from '@ali/lowcode-globals';
|
|
||||||
|
|
||||||
export interface LocateEvent {
|
export interface LocateEvent {
|
||||||
readonly type: 'LocateEvent';
|
readonly type: 'LocateEvent';
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { obx } from '@ali/lowcode-globals';
|
import { obx } from '@ali/lowcode-editor-core';
|
||||||
import { Node, DocumentModel } from '../document';
|
import { Node, DocumentModel } from '../document';
|
||||||
|
|
||||||
export class Hovering {
|
export class Hovering {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { obx, computed } from '@ali/lowcode-globals';
|
import { obx, computed } from '@ali/lowcode-editor-core';
|
||||||
import { uniqueId } from '@ali/lowcode-globals';
|
import { uniqueId } from '@ali/lowcode-utils';
|
||||||
import { INodeSelector, IViewport } from '../simulator';
|
import { INodeSelector, IViewport } from '../simulator';
|
||||||
import { isRootNode, Node } from '../document';
|
import { isRootNode, Node } from '../document';
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { isElement } from '@ali/lowcode-globals';
|
import { isElement } from '@ali/lowcode-utils';
|
||||||
|
|
||||||
export class ScrollTarget {
|
export class ScrollTarget {
|
||||||
get left() {
|
get left() {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { SettingTarget } from '@ali/lowcode-globals';
|
import { SettingTarget } from '@ali/lowcode-types';
|
||||||
import { ComponentMeta } from '../../component-meta';
|
import { ComponentMeta } from '../../component-meta';
|
||||||
import { Designer } from '../designer';
|
import { Designer } from '../designer';
|
||||||
import { Node } from '../../document';
|
import { Node } from '../../document';
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { TitleContent, computed, isDynamicSetter, SetterType, DynamicSetter, FieldExtraProps, FieldConfig, CustomView, isCustomView, obx } from '@ali/lowcode-globals';
|
import { TitleContent, isDynamicSetter, SetterType, DynamicSetter, FieldExtraProps, FieldConfig, CustomView, isCustomView } from '@ali/lowcode-types';
|
||||||
import { Transducer } from './utils';
|
import { Transducer } from './utils';
|
||||||
import { SettingPropEntry } from './setting-prop-entry';
|
import { SettingPropEntry } from './setting-prop-entry';
|
||||||
import { SettingEntry } from './setting-entry';
|
import { SettingEntry } from './setting-entry';
|
||||||
|
import { computed, obx } from '@ali/lowcode-editor-core';
|
||||||
|
|
||||||
export class SettingField extends SettingPropEntry implements SettingEntry {
|
export class SettingField extends SettingPropEntry implements SettingEntry {
|
||||||
readonly isSettingField = true;
|
readonly isSettingField = true;
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import { obx, uniqueId, computed, IEditor } from '@ali/lowcode-globals';
|
import { obx, computed } from '@ali/lowcode-editor-core';
|
||||||
|
import { IEditor } from '@ali/lowcode-types';
|
||||||
|
import { uniqueId } from '@ali/lowcode-utils';
|
||||||
import { SettingEntry } from './setting-entry';
|
import { SettingEntry } from './setting-entry';
|
||||||
import { Node } from '../../document';
|
import { Node } from '../../document';
|
||||||
import { ComponentMeta } from '../../component-meta';
|
import { ComponentMeta } from '../../component-meta';
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { CustomView, computed, isCustomView, IEditor } from '@ali/lowcode-globals';
|
import { CustomView, isCustomView, IEditor } from '@ali/lowcode-types';
|
||||||
|
import { computed } from '@ali/lowcode-editor-core';
|
||||||
import { SettingEntry } from './setting-entry';
|
import { SettingEntry } from './setting-entry';
|
||||||
import { SettingField } from './setting-field';
|
import { SettingField } from './setting-field';
|
||||||
import { SettingPropEntry } from './setting-prop-entry';
|
import { SettingPropEntry } from './setting-prop-entry';
|
||||||
|
|||||||
@ -1,17 +1,5 @@
|
|||||||
import {
|
import { computed, obx } from '@ali/lowcode-editor-core';
|
||||||
NodeData,
|
import { NodeData, isJSExpression, isDOMText, NodeSchema, isNodeSchema, RootSchema } from '@ali/lowcode-types';
|
||||||
isJSExpression,
|
|
||||||
isDOMText,
|
|
||||||
NodeSchema,
|
|
||||||
computed,
|
|
||||||
obx,
|
|
||||||
autorun,
|
|
||||||
isNodeSchema,
|
|
||||||
uniqueId,
|
|
||||||
PageSchema,
|
|
||||||
ComponentSchema,
|
|
||||||
RootSchema,
|
|
||||||
} from '@ali/lowcode-globals';
|
|
||||||
import { Project } from '../project';
|
import { Project } from '../project';
|
||||||
import { ISimulatorHost } from '../simulator';
|
import { ISimulatorHost } from '../simulator';
|
||||||
import { ComponentMeta } from '../component-meta';
|
import { ComponentMeta } from '../component-meta';
|
||||||
@ -20,6 +8,7 @@ import { Node, insertChildren, insertChild, isNode, RootNode, ParentalNode } fro
|
|||||||
import { Selection } from './selection';
|
import { Selection } from './selection';
|
||||||
import { History } from './history';
|
import { History } from './history';
|
||||||
import { TransformStage } from './node';
|
import { TransformStage } from './node';
|
||||||
|
import { uniqueId } from '@ali/lowcode-utils';
|
||||||
|
|
||||||
export type GetDataType<T, NodeType> = T extends undefined
|
export type GetDataType<T, NodeType> = T extends undefined
|
||||||
? NodeType extends {
|
? NodeType extends {
|
||||||
@ -90,11 +79,13 @@ export class DocumentModel {
|
|||||||
this._blank = true;
|
this._blank = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rootNode = this.createNode<RootNode>(schema || {
|
this.rootNode = this.createNode<RootNode>(
|
||||||
componentName: 'Page',
|
schema || {
|
||||||
id: 'root',
|
componentName: 'Page',
|
||||||
fileName: ''
|
id: 'root',
|
||||||
});
|
fileName: '',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
this.history = new History(
|
this.history = new History(
|
||||||
() => this.schema,
|
() => this.schema,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { observer } from '@ali/lowcode-globals';
|
import { observer } from '@ali/lowcode-editor-core';
|
||||||
import { DocumentModel } from './document-model';
|
import { DocumentModel } from './document-model';
|
||||||
import { BuiltinSimulatorHostView } from '../builtin-simulator';
|
import { BuiltinSimulatorHostView } from '../builtin-simulator';
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { NodeSchema, autorun, Reaction, untracked } from '@ali/lowcode-globals';
|
import { autorun, Reaction, untracked } from '@ali/lowcode-editor-core';
|
||||||
|
import { NodeSchema } from '@ali/lowcode-types';
|
||||||
|
|
||||||
// TODO: cache to localStorage
|
// TODO: cache to localStorage
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { obx, computed, TitleContent } from '@ali/lowcode-globals';
|
import { obx, computed } from '@ali/lowcode-editor-core';
|
||||||
import { uniqueId } from '@ali/lowcode-globals';
|
import { uniqueId } from '@ali/lowcode-utils';
|
||||||
|
import { TitleContent } from '@ali/lowcode-types';
|
||||||
import { Node } from './node';
|
import { Node } from './node';
|
||||||
import { intl } from '../../locale';
|
import { intl } from '../../locale';
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { NodeData, isNodeSchema, obx, computed } from '@ali/lowcode-globals';
|
import { obx, computed } from '@ali/lowcode-editor-core';
|
||||||
import { Node, ParentalNode } from './node';
|
import { Node, ParentalNode } from './node';
|
||||||
import { TransformStage } from './transform-stage';
|
import { TransformStage } from './transform-stage';
|
||||||
|
import { NodeData, isNodeSchema } from '@ali/lowcode-types';
|
||||||
|
|
||||||
export class NodeChildren {
|
export class NodeChildren {
|
||||||
@obx.val private children: Node[];
|
@obx.val private children: Node[];
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { obx, computed } from '@ali/lowcode-editor-core';
|
||||||
import {
|
import {
|
||||||
isDOMText,
|
isDOMText,
|
||||||
isJSExpression,
|
isJSExpression,
|
||||||
@ -6,12 +7,10 @@ import {
|
|||||||
PropsList,
|
PropsList,
|
||||||
NodeData,
|
NodeData,
|
||||||
TitleContent,
|
TitleContent,
|
||||||
obx,
|
|
||||||
computed,
|
|
||||||
SlotSchema,
|
SlotSchema,
|
||||||
PageSchema,
|
PageSchema,
|
||||||
ComponentSchema,
|
ComponentSchema,
|
||||||
} from '@ali/lowcode-globals';
|
} from '@ali/lowcode-types';
|
||||||
import { Props, EXTRA_KEY_PREFIX } from './props/props';
|
import { Props, EXTRA_KEY_PREFIX } from './props/props';
|
||||||
import { DocumentModel } from '../document-model';
|
import { DocumentModel } from '../document-model';
|
||||||
import { NodeChildren } from './node-children';
|
import { NodeChildren } from './node-children';
|
||||||
@ -20,8 +19,6 @@ import { ComponentMeta } from '../../component-meta';
|
|||||||
import { ExclusiveGroup, isExclusiveGroup } from './exclusive-group';
|
import { ExclusiveGroup, isExclusiveGroup } from './exclusive-group';
|
||||||
import { TransformStage } from './transform-stage';
|
import { TransformStage } from './transform-stage';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础节点
|
* 基础节点
|
||||||
*
|
*
|
||||||
@ -175,7 +172,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isRoot(): this is RootNode {
|
isRoot(): this is RootNode {
|
||||||
return this.document.rootNode == this as any;
|
return this.document.rootNode == (this as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
isPage(): this is PageNode {
|
isPage(): this is PageNode {
|
||||||
@ -298,7 +295,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
|||||||
@computed get slots() {
|
@computed get slots() {
|
||||||
// TODO: optimize recore/obx, array maked every time, donot as changed
|
// TODO: optimize recore/obx, array maked every time, donot as changed
|
||||||
const slots: Node[] = [];
|
const slots: Node[] = [];
|
||||||
this.props.forEach(item => {
|
this.props.forEach((item) => {
|
||||||
if (item.type === 'slot') {
|
if (item.type === 'slot') {
|
||||||
slots.push(item.slotNode!);
|
slots.push(item.slotNode!);
|
||||||
}
|
}
|
||||||
@ -563,7 +560,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
|||||||
this.children?.insert(node, ref ? ref.index : null);
|
this.children?.insert(node, ref ? ref.index : null);
|
||||||
}
|
}
|
||||||
insertAfter(node: Node, ref?: Node) {
|
insertAfter(node: Node, ref?: Node) {
|
||||||
this.children?.insert(node, ref ? (ref.index + 1) : null);
|
this.children?.insert(node, ref ? ref.index + 1 : null);
|
||||||
}
|
}
|
||||||
getParent() {
|
getParent() {
|
||||||
return this.parent;
|
return this.parent;
|
||||||
@ -594,9 +591,7 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
|
|||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
setStatus() {
|
setStatus() {}
|
||||||
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { obx, autorun, untracked, computed } from '@ali/lowcode-globals';
|
import { obx, autorun, untracked, computed } from '@ali/lowcode-editor-core';
|
||||||
import { Prop, IPropParent, UNSET } from './prop';
|
import { Prop, IPropParent, UNSET } from './prop';
|
||||||
import { Props } from './props';
|
import { Props } from './props';
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,6 @@
|
|||||||
import {
|
import { untracked, computed, obx } from '@ali/lowcode-editor-core';
|
||||||
CompositeValue,
|
import { CompositeValue, isJSExpression, isJSSlot, JSSlot, SlotSchema } from '@ali/lowcode-types';
|
||||||
isJSExpression,
|
import { uniqueId, isPlainObject, hasOwnProperty } from '@ali/lowcode-utils';
|
||||||
isJSSlot,
|
|
||||||
untracked,
|
|
||||||
computed,
|
|
||||||
obx,
|
|
||||||
JSSlot,
|
|
||||||
SlotSchema
|
|
||||||
} from '@ali/lowcode-globals';
|
|
||||||
import { uniqueId } from '@ali/lowcode-globals';
|
|
||||||
import { isPlainObject } from '@ali/lowcode-globals';
|
|
||||||
import { hasOwnProperty } from '@ali/lowcode-globals';
|
|
||||||
import { PropStash } from './prop-stash';
|
import { PropStash } from './prop-stash';
|
||||||
import { valueToSource } from './value-to-source';
|
import { valueToSource } from './value-to-source';
|
||||||
import { Props } from './props';
|
import { Props } from './props';
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { PropsMap, PropsList, CompositeValue, computed, obx, uniqueId } from '@ali/lowcode-globals';
|
import { computed, obx } from '@ali/lowcode-editor-core';
|
||||||
|
import { PropsMap, PropsList, CompositeValue } from '@ali/lowcode-types';
|
||||||
|
import { uniqueId } from '@ali/lowcode-utils';
|
||||||
import { PropStash } from './prop-stash';
|
import { PropStash } from './prop-stash';
|
||||||
import { Prop, IPropParent, UNSET } from './prop';
|
import { Prop, IPropParent, UNSET } from './prop';
|
||||||
import { Node } from '../node';
|
import { Node } from '../node';
|
||||||
import { TransformStage } from '../transform-stage';
|
import { TransformStage } from '../transform-stage';
|
||||||
|
|
||||||
export const EXTRA_KEY_PREFIX = '__';
|
export const EXTRA_KEY_PREFIX = '___';
|
||||||
|
|
||||||
export class Props implements IPropParent {
|
export class Props implements IPropParent {
|
||||||
readonly id = uniqueId('props');
|
readonly id = uniqueId('props');
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { obx } from '@ali/lowcode-globals';
|
import { obx } from '@ali/lowcode-editor-core';
|
||||||
import { Node, comparePosition, PositionNO } from './node/node';
|
import { Node, comparePosition, PositionNO } from './node/node';
|
||||||
import { DocumentModel } from './document-model';
|
import { DocumentModel } from './document-model';
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { SVGIcon, IconProps } from "@ali/lowcode-globals";
|
import { SVGIcon, IconProps } from "@ali/lowcode-utils";
|
||||||
|
|
||||||
export function IconClone(props: IconProps) {
|
export function IconClone(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { SVGIcon, IconProps } from "@ali/lowcode-globals";
|
import { SVGIcon, IconProps } from "@ali/lowcode-utils";
|
||||||
|
|
||||||
export function IconComponent(props: IconProps) {
|
export function IconComponent(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { SVGIcon, IconProps } from "@ali/lowcode-globals";
|
import { SVGIcon, IconProps } from "@ali/lowcode-utils";
|
||||||
|
|
||||||
export function IconContainer(props: IconProps) {
|
export function IconContainer(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { SVGIcon, IconProps } from "@ali/lowcode-globals";
|
import { SVGIcon, IconProps } from "@ali/lowcode-utils";
|
||||||
|
|
||||||
export function IconHidden(props: IconProps) {
|
export function IconHidden(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { SVGIcon, IconProps } from "@ali/lowcode-globals";
|
import { SVGIcon, IconProps } from "@ali/lowcode-utils";
|
||||||
|
|
||||||
export function IconPage(props: IconProps) {
|
export function IconPage(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { SVGIcon, IconProps } from '@ali/lowcode-globals';
|
import { SVGIcon, IconProps } from '@ali/lowcode-utils';
|
||||||
|
|
||||||
export function IconRemove(props: IconProps) {
|
export function IconRemove(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { SVGIcon, IconProps } from '@ali/lowcode-globals';
|
import { SVGIcon, IconProps } from '@ali/lowcode-utils';
|
||||||
|
|
||||||
export function IconSetting(props: IconProps) {
|
export function IconSetting(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -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 en_US from './en-US.json';
|
||||||
import zh_CN from './zh-CN.json';
|
import zh_CN from './zh-CN.json';
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import { observer } from '@ali/lowcode-globals';
|
import { observer } from '@ali/lowcode-editor-core';
|
||||||
import { Designer } from '../designer';
|
import { Designer } from '../designer';
|
||||||
import { DocumentView } from '../document';
|
import { DocumentView } from '../document';
|
||||||
import { intl } from '../locale';
|
import { intl } from '../locale';
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { ProjectSchema, RootSchema, obx, computed } from '@ali/lowcode-globals';
|
import { obx, computed } from '@ali/lowcode-editor-core';
|
||||||
import { Designer } from '../designer';
|
import { Designer } from '../designer';
|
||||||
import { DocumentModel, isDocumentModel } from '../document';
|
import { DocumentModel, isDocumentModel } from '../document';
|
||||||
|
import { ProjectSchema, RootSchema } from '@ali/lowcode-types';
|
||||||
|
|
||||||
export class Project {
|
export class Project {
|
||||||
private emitter = new EventEmitter();
|
private emitter = new EventEmitter();
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Component as ReactComponent, ComponentType } from 'react';
|
import { Component as ReactComponent, ComponentType } from 'react';
|
||||||
import { ComponentMetadata } from '@ali/lowcode-globals';
|
import { ComponentMetadata } from '@ali/lowcode-types';
|
||||||
import { ISensor, Point, ScrollTarget, IScrollable } from './designer';
|
import { ISensor, Point, ScrollTarget, IScrollable } from './designer';
|
||||||
import { Node } from './document';
|
import { Node } from './document';
|
||||||
|
|
||||||
|
|||||||
@ -1,47 +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.6"></a>
|
|
||||||
## [0.8.6](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.5...@ali/lowcode-editor-core@0.8.6) (2020-04-16)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @ali/lowcode-editor-core
|
|
||||||
|
|
||||||
<a name="0.8.5"></a>
|
|
||||||
## [0.8.5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.4...@ali/lowcode-editor-core@0.8.5) (2020-04-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* editor ([ccd9162](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ccd9162))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a name="0.8.4"></a>
|
|
||||||
## [0.8.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.3...@ali/lowcode-editor-core@0.8.4) (2020-03-30)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @ali/lowcode-editor-core
|
|
||||||
|
|
||||||
<a name="0.8.3"></a>
|
|
||||||
## 0.8.3 (2020-03-30)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @ali/lowcode-editor-core
|
|
||||||
|
|
||||||
<a name="0.8.2"></a>
|
|
||||||
## 0.8.2 (2020-03-30)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Note:** Version bump only for package @ali/lowcode-editor-core
|
|
||||||
@ -1,11 +1,3 @@
|
|||||||
# demo component
|
shared globals
|
||||||
|
|
||||||
t-s-demo
|
发 CDN
|
||||||
|
|
||||||
intro component
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
| 参数名 | 说明 | 必填 | 类型 | 默认值 | 备注 |
|
|
||||||
| ------ | ---- | ---- | ---- | ------ | ---- |
|
|
||||||
| | | | | | |
|
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"build-plugin-component"
|
"build-plugin-component",
|
||||||
|
"build-plugin-fusion",
|
||||||
|
["build-plugin-moment-locales", {
|
||||||
|
"locales": ["zh-cn"]
|
||||||
|
}]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,45 +1,56 @@
|
|||||||
{
|
{
|
||||||
"name": "@ali/lowcode-editor-core",
|
"name": "@ali/lowcode-globals",
|
||||||
"version": "0.8.6",
|
"version": "0.9.3",
|
||||||
"description": "alibaba lowcode editor core",
|
"description": "Globals api for Ali lowCode engine",
|
||||||
|
"license": "MIT",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"module": "es/index.js",
|
"module": "es/index.js",
|
||||||
"stylePath": "style.js",
|
|
||||||
"files": [
|
"files": [
|
||||||
"lib",
|
"lib",
|
||||||
"es"
|
"es"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "build-scripts build --skip-demo",
|
"build": "build-scripts build --skip-demo",
|
||||||
|
"cloud-build": "build-scripts build --skip-demo --config cloud-build.json",
|
||||||
"test": "ava",
|
"test": "ava",
|
||||||
"test:snapshot": "ava --update-snapshots"
|
"test:snapshot": "ava --update-snapshots"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"ava": {
|
||||||
"lowcode",
|
"compileEnhancements": false,
|
||||||
"editor"
|
"extensions": [
|
||||||
],
|
"ts"
|
||||||
"author": "xiayang.xy",
|
],
|
||||||
|
"require": [
|
||||||
|
"ts-node/register"
|
||||||
|
],
|
||||||
|
"snapshotDir": "test/fixtures/__snapshots__"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ali/lowcode-globals": "^0.9.3",
|
"@alifd/next": "^1.19.16",
|
||||||
"@alifd/next": "1.x",
|
"@recore/obx": "^1.0.8",
|
||||||
|
"@recore/obx-react": "^1.0.7",
|
||||||
|
"classnames": "^2.2.6",
|
||||||
|
"power-di": "^2.2.4",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"events": "^3.1.0",
|
|
||||||
"intl-messageformat": "^8.3.1",
|
"intl-messageformat": "^8.3.1",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
"prop-types": "^15.5.8",
|
|
||||||
"react": "^16.8.0",
|
|
||||||
"store": "^2.0.12",
|
"store": "^2.0.12",
|
||||||
"whatwg-fetch": "^3.0.0"
|
"react": "^16",
|
||||||
|
"react-dom": "^16.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@alib/build-scripts": "^0.1.3",
|
"@alib/build-scripts": "^0.1.18",
|
||||||
|
"@types/classnames": "^2.2.7",
|
||||||
|
"@types/node": "^13.7.1",
|
||||||
|
"@types/react": "^16",
|
||||||
|
"@types/react-dom": "^16",
|
||||||
|
|
||||||
"@types/lodash": "^4.14.149",
|
"@types/lodash": "^4.14.149",
|
||||||
"@types/react": "^16.9.13",
|
|
||||||
"@types/react-dom": "^16.9.4",
|
|
||||||
"@types/store": "^2.0.2",
|
"@types/store": "^2.0.2",
|
||||||
"build-plugin-component": "^0.2.10"
|
"build-plugin-component": "^0.2.11",
|
||||||
|
"build-plugin-fusion": "^0.1.0",
|
||||||
|
"build-plugin-moment-locales": "^0.1.0"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://registry.npm.alibaba-inc.com"
|
"registry": "https://registry.npm.alibaba-inc.com"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,75 +0,0 @@
|
|||||||
import { PluginConfig, PluginStatus, PluginClass, HOCPlugin } from './definitions';
|
|
||||||
import Editor from './index';
|
|
||||||
import { clone, deepEqual } from './utils';
|
|
||||||
|
|
||||||
export default class AreaManager {
|
|
||||||
private pluginStatus: PluginStatus;
|
|
||||||
|
|
||||||
private config: PluginConfig[];
|
|
||||||
|
|
||||||
constructor(private editor: Editor, private name: string) {
|
|
||||||
this.config = (editor && editor.config && editor.config.plugins && editor.config.plugins[name]) || [];
|
|
||||||
this.pluginStatus = clone(editor.pluginStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
setVisible(flag: boolean) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
isEnable() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
isVisible() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
isEmpty() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
isPluginStatusUpdate(pluginType?: string, notUpdateStatus?: boolean): boolean {
|
|
||||||
const { pluginStatus } = this.editor;
|
|
||||||
const list = pluginType ? this.config.filter((item): boolean => item.type === pluginType) : this.config;
|
|
||||||
|
|
||||||
const isUpdate = list.some(
|
|
||||||
(item): boolean => !deepEqual(pluginStatus[item.pluginKey], this.pluginStatus[item.pluginKey]),
|
|
||||||
);
|
|
||||||
if (!notUpdateStatus) {
|
|
||||||
this.pluginStatus = clone(pluginStatus);
|
|
||||||
}
|
|
||||||
return isUpdate;
|
|
||||||
}
|
|
||||||
|
|
||||||
getVisiblePluginList(pluginType?: string): PluginConfig[] {
|
|
||||||
const res = this.config.filter((item): boolean => {
|
|
||||||
return !!(!this.pluginStatus[item.pluginKey] || this.pluginStatus[item.pluginKey].visible);
|
|
||||||
});
|
|
||||||
return pluginType ? res.filter((item): boolean => item.type === pluginType) : res;
|
|
||||||
}
|
|
||||||
|
|
||||||
getPlugin(pluginKey: string): HOCPlugin | void {
|
|
||||||
if (pluginKey) {
|
|
||||||
return this.editor && this.editor.plugins && this.editor.plugins[pluginKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPluginConfig(pluginKey?: string): PluginConfig[] | PluginConfig | undefined {
|
|
||||||
if (pluginKey) {
|
|
||||||
return this.config.find(item => item.pluginKey === pluginKey);
|
|
||||||
}
|
|
||||||
return this.config;
|
|
||||||
}
|
|
||||||
|
|
||||||
getPluginClass(pluginKey: string): PluginClass | void {
|
|
||||||
if (pluginKey) {
|
|
||||||
return this.editor && this.editor.components && this.editor.components[pluginKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPluginStatus(pluginKey: string): PluginStatus | void {
|
|
||||||
if (pluginKey) {
|
|
||||||
return this.editor && this.editor.pluginStatus && this.editor.pluginStatus[pluginKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
import { createContext } from 'react';
|
|
||||||
|
|
||||||
const context = createContext({});
|
|
||||||
export default context;
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
export * from './setter';
|
export * from './setter';
|
||||||
export * from './transducer';
|
|
||||||
export * from './ioc-context';
|
export * from './ioc-context';
|
||||||
export * from './editor';
|
export * from './tip';
|
||||||
@ -1,7 +1,6 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { CustomView, isCustomView } from '../types/setter-config';
|
import { CustomView, isCustomView, TitleContent } from '@ali/lowcode-types';
|
||||||
import { createContent } from '../utils/create-content';
|
import { createContent } from '@ali/lowcode-utils';
|
||||||
import { TitleContent } from '../types';
|
|
||||||
|
|
||||||
export type RegisteredSetter = {
|
export type RegisteredSetter = {
|
||||||
component: CustomView;
|
component: CustomView;
|
||||||
@ -1,31 +1,10 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import store from 'store';
|
import { IEditor, EditorConfig, PluginClassSet } from '@ali/lowcode-types';
|
||||||
import { IocContext, RegisterOptions } from '@ali/lowcode-globals';
|
import { IocContext, RegisterOptions } from './di';
|
||||||
import {
|
import { globalLocale } from './intl';
|
||||||
EditorConfig,
|
|
||||||
HooksConfig,
|
|
||||||
LocaleType,
|
|
||||||
PluginStatusSet,
|
|
||||||
Utils,
|
|
||||||
PluginClassSet,
|
|
||||||
PluginSet,
|
|
||||||
} from './definitions';
|
|
||||||
|
|
||||||
import pluginFactory from './pluginFactory';
|
|
||||||
|
|
||||||
import * as editorUtils from './utils';
|
|
||||||
|
|
||||||
const { registShortCuts, transformToPromise, unRegistShortCuts } = editorUtils;
|
|
||||||
|
|
||||||
let instance: Editor;
|
|
||||||
|
|
||||||
EventEmitter.defaultMaxListeners = 100;
|
EventEmitter.defaultMaxListeners = 100;
|
||||||
|
|
||||||
export interface HooksFuncs {
|
export type KeyType = Function | Symbol | string;
|
||||||
[idx: number]: (msg: string, handler: (...args: []) => void) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type KeyType = Function | symbol | string;
|
|
||||||
export type ClassType = Function | (new (...args: any[]) => any);
|
export type ClassType = Function | (new (...args: any[]) => any);
|
||||||
export interface GetOptions {
|
export interface GetOptions {
|
||||||
forceNew?: boolean;
|
forceNew?: boolean;
|
||||||
@ -41,26 +20,7 @@ export type GetReturnType<T, ClsType> = T extends undefined
|
|||||||
|
|
||||||
const NOT_FOUND = Symbol.for('not_found');
|
const NOT_FOUND = Symbol.for('not_found');
|
||||||
|
|
||||||
export default class Editor extends EventEmitter {
|
export class Editor extends EventEmitter implements IEditor {
|
||||||
static getInstance = (config: EditorConfig, components: PluginClassSet, utils?: Utils): Editor => {
|
|
||||||
if (!instance) {
|
|
||||||
instance = new Editor(config, components, utils);
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
private _components?: PluginClassSet;
|
|
||||||
get components(): PluginClassSet {
|
|
||||||
if (!this._components) {
|
|
||||||
this._components = {};
|
|
||||||
Object.keys(this.componentsMap).forEach((key) => {
|
|
||||||
(this._components as any)[key] = pluginFactory(this.componentsMap[key]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this._components;
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly utils: Utils;
|
|
||||||
/**
|
/**
|
||||||
* Ioc Container
|
* Ioc Container
|
||||||
*/
|
*/
|
||||||
@ -68,18 +28,12 @@ export default class Editor extends EventEmitter {
|
|||||||
notFoundHandler: (type: KeyType) => NOT_FOUND,
|
notFoundHandler: (type: KeyType) => NOT_FOUND,
|
||||||
});
|
});
|
||||||
|
|
||||||
pluginStatus?: PluginStatusSet;
|
get locale() {
|
||||||
|
return globalLocale.getLocale();
|
||||||
|
}
|
||||||
|
|
||||||
plugins?: PluginSet;
|
constructor(readonly config: EditorConfig = {}, readonly components: PluginClassSet = {}) {
|
||||||
|
|
||||||
locale?: LocaleType;
|
|
||||||
|
|
||||||
hooksFuncs?: HooksFuncs;
|
|
||||||
|
|
||||||
constructor(readonly config: EditorConfig = {}, readonly componentsMap: PluginClassSet = {}, utils?: Utils) {
|
|
||||||
super();
|
super();
|
||||||
this.utils = { ...editorUtils, ...utils } as any;
|
|
||||||
instance = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get<T = undefined, KeyOrType = any>(keyOrType: KeyOrType, opt?: GetOptions): GetReturnType<T, KeyOrType> | undefined {
|
get<T = undefined, KeyOrType = any>(keyOrType: KeyOrType, opt?: GetOptions): GetReturnType<T, KeyOrType> | undefined {
|
||||||
@ -135,18 +89,15 @@ export default class Editor extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async init(): Promise<any> {
|
async init(): Promise<any> {
|
||||||
const { hooks, shortCuts = [], lifeCycles } = this.config || {};
|
const { shortCuts = [], lifeCycles } = this.config || {};
|
||||||
this.locale = store.get('lowcode-editor-locale') || 'zh-CN';
|
|
||||||
this.pluginStatus = this.initPluginStatus();
|
|
||||||
this.initHooks(hooks || []);
|
|
||||||
|
|
||||||
this.emit('editor.beforeInit');
|
this.emit('editor.beforeInit');
|
||||||
const init = (lifeCycles && lifeCycles.init) || ((): void => {});
|
const init = (lifeCycles && lifeCycles.init) || ((): void => {});
|
||||||
// 用户可以通过设置extensions.init自定义初始化流程;
|
// 用户可以通过设置extensions.init自定义初始化流程;
|
||||||
try {
|
try {
|
||||||
await transformToPromise(init(this));
|
// await transformToPromise(init(this));
|
||||||
// 注册快捷键
|
// 注册快捷键
|
||||||
registShortCuts(shortCuts, this);
|
// registShortCuts(shortCuts, this);
|
||||||
this.emit('editor.afterInit');
|
this.emit('editor.afterInit');
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -156,9 +107,8 @@ export default class Editor extends EventEmitter {
|
|||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
try {
|
try {
|
||||||
const { hooks = [], shortCuts = [], lifeCycles = {} } = this.config;
|
const { shortCuts = [], lifeCycles = {} } = this.config;
|
||||||
unRegistShortCuts(shortCuts);
|
// unRegistShortCuts(shortCuts);
|
||||||
this.destroyHooks(hooks);
|
|
||||||
if (lifeCycles.destroy) {
|
if (lifeCycles.destroy) {
|
||||||
lifeCycles.destroy(this);
|
lifeCycles.destroy(this);
|
||||||
}
|
}
|
||||||
@ -167,33 +117,6 @@ export default class Editor extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
batchOn(events: string[], lisenter: (...args: any[]) => void): void {
|
|
||||||
if (!Array.isArray(events)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
events.forEach((event): void => {
|
|
||||||
this.on(event, lisenter);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
batchOnce(events: string[], lisenter: (...args: any[]) => void): void {
|
|
||||||
if (!Array.isArray(events)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
events.forEach((event): void => {
|
|
||||||
this.once(event, lisenter);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
batchOff(events: string[], lisenter: (...args: any[]) => void): void {
|
|
||||||
if (!Array.isArray(events)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
events.forEach((event): void => {
|
|
||||||
this.off(event, lisenter);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private waits = new Map<
|
private waits = new Map<
|
||||||
KeyType,
|
KeyType,
|
||||||
Array<{
|
Array<{
|
||||||
@ -245,50 +168,4 @@ export default class Editor extends EventEmitter {
|
|||||||
this.waits.delete(key);
|
this.waits.delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 销毁hooks中的消息监听
|
|
||||||
private destroyHooks(hooks: HooksConfig = []): void {
|
|
||||||
hooks.forEach((item, idx): void => {
|
|
||||||
if (typeof this.hooksFuncs?.[idx] === 'function') {
|
|
||||||
this.off(item.message, this.hooksFuncs[idx]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
delete this.hooksFuncs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化hooks中的消息监听
|
|
||||||
private initHooks(hooks: HooksConfig = []): void {
|
|
||||||
this.hooksFuncs = hooks.map((item): ((...arg: any[]) => void) => {
|
|
||||||
const func = (...args: any[]): void => {
|
|
||||||
item.handler(this, ...args);
|
|
||||||
};
|
|
||||||
this[item.type](item.message, func);
|
|
||||||
return func;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private initPluginStatus(): PluginStatusSet {
|
|
||||||
const { plugins = {} } = this.config;
|
|
||||||
const pluginAreas = Object.keys(plugins);
|
|
||||||
const res: PluginStatusSet = {};
|
|
||||||
pluginAreas.forEach((area): void => {
|
|
||||||
(plugins[area] || []).forEach((plugin): void => {
|
|
||||||
if (plugin.type === 'Divider') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { visible, disabled, marked } = plugin.props || {};
|
|
||||||
res[plugin.pluginKey] = {
|
|
||||||
visible: typeof visible === 'boolean' ? visible : true,
|
|
||||||
disabled: typeof disabled === 'boolean' ? disabled : false,
|
|
||||||
marked: typeof marked === 'boolean' ? marked : false,
|
|
||||||
};
|
|
||||||
const pluginClass = this.components[plugin.pluginKey];
|
|
||||||
// 判断如果编辑器插件有init静态方法,则在此执行init方法
|
|
||||||
if (pluginClass && pluginClass.init) {
|
|
||||||
pluginClass.init(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,6 @@
|
|||||||
import Editor from './editor';
|
export * from './intl';
|
||||||
|
export * from './editor';
|
||||||
import * as utils from './utils';
|
export * from './utils';
|
||||||
|
export * from './di';
|
||||||
export { default as PluginFactory } from './pluginFactory';
|
export * from './hotkey';
|
||||||
export { default as EditorContext } from './context';
|
export * from './widgets';
|
||||||
export { default as AreaManager } from './areaManager';
|
|
||||||
|
|
||||||
export default Editor;
|
|
||||||
|
|
||||||
export { Editor, utils };
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
import { obx } from '../utils/obx';
|
||||||
const languageMap: { [key: string]: string } = {
|
const languageMap: { [key: string]: string } = {
|
||||||
en: 'en-US',
|
en: 'en-US',
|
||||||
zh: 'zh-CN',
|
zh: 'zh-CN',
|
||||||
@ -28,7 +29,7 @@ const languageMap: { [key: string]: string } = {
|
|||||||
const LowcodeConfigKey = 'ali-lowcode-config';
|
const LowcodeConfigKey = 'ali-lowcode-config';
|
||||||
|
|
||||||
class AliGlobalLocale {
|
class AliGlobalLocale {
|
||||||
private locale: string = '';
|
@obx.ref private locale: string = '';
|
||||||
private emitter = new EventEmitter();
|
private emitter = new EventEmitter();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -1,19 +1,9 @@
|
|||||||
|
import { ReactNode, Component, createElement } from 'react';
|
||||||
|
import { IntlMessageFormat } from 'intl-messageformat';
|
||||||
import { globalLocale } from './ali-global-locale';
|
import { globalLocale } from './ali-global-locale';
|
||||||
import { PureComponent, ReactNode } from 'react';
|
import { isI18nData } from '@ali/lowcode-types';
|
||||||
import { isI18nData } from '../types';
|
import { observer, computed } from '../utils';
|
||||||
|
|
||||||
function injectVars(template: string, params: any): string {
|
|
||||||
if (!template || !params) {
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
return template.replace(/({\w+})/g, (_, $1) => {
|
|
||||||
const key = (/\d+/.exec($1) || [])[0] as any;
|
|
||||||
if (key && params[key] != null) {
|
|
||||||
return params[key];
|
|
||||||
}
|
|
||||||
return $1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function generateTryLocales(locale: string) {
|
function generateTryLocales(locale: string) {
|
||||||
const tries = [locale, locale.replace('-', '_')];
|
const tries = [locale, locale.replace('-', '_')];
|
||||||
if (locale === 'zh-TW' || locale === 'en-US') {
|
if (locale === 'zh-TW' || locale === 'en-US') {
|
||||||
@ -30,44 +20,40 @@ function generateTryLocales(locale: string) {
|
|||||||
return tries;
|
return tries;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function localeFormat(data: any, params?: object): string {
|
function injectVars(msg: string, params: any, locale: string): string {
|
||||||
|
if (!msg || !params) {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
const formater = new IntlMessageFormat(msg, locale);
|
||||||
|
return formater.format(params as any) as string;
|
||||||
|
/*
|
||||||
|
|
||||||
|
return template.replace(/({\w+})/g, (_, $1) => {
|
||||||
|
const key = (/\d+/.exec($1) || [])[0] as any;
|
||||||
|
if (key && params[key] != null) {
|
||||||
|
return params[key];
|
||||||
|
}
|
||||||
|
return $1;
|
||||||
|
});*/
|
||||||
|
}
|
||||||
|
|
||||||
|
export function intl(data: any, params?: object): string {
|
||||||
if (!isI18nData(data)) {
|
if (!isI18nData(data)) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
const locale = globalLocale.getLocale();
|
const locale = globalLocale.getLocale();
|
||||||
const tries = generateTryLocales(locale);
|
const tries = generateTryLocales(locale);
|
||||||
let tpl: string | undefined;
|
let msg: string | undefined;
|
||||||
for (const lan of tries) {
|
for (const lan of tries) {
|
||||||
tpl = data[lan];
|
msg = data[lan];
|
||||||
if (tpl != null) {
|
if (msg != null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tpl == null) {
|
if (msg == null) {
|
||||||
return `##intl@${locale}##`;
|
return `##intl@${locale}##`;
|
||||||
}
|
}
|
||||||
return injectVars(tpl, params);
|
return injectVars(msg, params, locale);
|
||||||
}
|
|
||||||
|
|
||||||
class Intl extends PureComponent<{ data: any; params?: object }> {
|
|
||||||
private dispose = globalLocale.onLocaleChange(() => this.forceUpdate());
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.dispose();
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
const { data, params } = this.props;
|
|
||||||
return localeFormat(data, params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function intl(data: any, params?: object): ReactNode {
|
|
||||||
if (isI18nData(data)) {
|
|
||||||
if (data.intl) {
|
|
||||||
return data.intl;
|
|
||||||
}
|
|
||||||
return <Intl data={data} params={params} />;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shallowIntl(data: any): any {
|
export function shallowIntl(data: any): any {
|
||||||
@ -76,38 +62,53 @@ export function shallowIntl(data: any): any {
|
|||||||
}
|
}
|
||||||
const maps: any = {};
|
const maps: any = {};
|
||||||
Object.keys(data).forEach(key => {
|
Object.keys(data).forEach(key => {
|
||||||
maps[key] = localeFormat(data[key]);
|
maps[key] = intl(data[key]);
|
||||||
});
|
});
|
||||||
return maps;
|
return maps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function intlNode(data: any, params?: object): ReactNode {
|
||||||
|
if (isI18nData(data)) {
|
||||||
|
if (data.intlNode) {
|
||||||
|
return data.intlNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return createElement(IntlElement, { data, params });
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
|
class IntlElement extends Component<{ data: any; params?: object }> {
|
||||||
|
render() {
|
||||||
|
const { data, params } = this.props;
|
||||||
|
return intl(data, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function createIntl(
|
export function createIntl(
|
||||||
instance: string | object,
|
instance: string | object,
|
||||||
): {
|
): {
|
||||||
intl(id: string, params?: object): ReactNode;
|
intlNode(id: string, params?: object): ReactNode;
|
||||||
intlString(id: string, params?: object): string;
|
intl(id: string, params?: object): string;
|
||||||
getLocale(): string;
|
getLocale(): string;
|
||||||
setLocale(locale: string): void;
|
setLocale(locale: string): void;
|
||||||
} {
|
} {
|
||||||
let lastLocale: string | undefined;
|
const data = computed(() => {
|
||||||
let data: any = {};
|
const locale = globalLocale.getLocale();
|
||||||
function useLocale(locale: string) {
|
|
||||||
lastLocale = locale;
|
|
||||||
if (typeof instance === 'string') {
|
if (typeof instance === 'string') {
|
||||||
if ((window as any)[instance]) {
|
if ((window as any)[instance]) {
|
||||||
data = (window as any)[instance][locale] || {};
|
data.messages = (window as any)[instance][locale] || {};
|
||||||
} else {
|
} else {
|
||||||
const key = `${instance}_${locale.toLocaleLowerCase()}`;
|
const key = `${instance}_${locale.toLocaleLowerCase()}`;
|
||||||
data = (window as any)[key] || {};
|
data.messages = (window as any)[key] || {};
|
||||||
}
|
}
|
||||||
} else if (instance && typeof instance === 'object') {
|
} else if (instance && typeof instance === 'object') {
|
||||||
data = (instance as any)[locale] || {};
|
data.messages = (instance as any)[locale] || {};
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
useLocale(globalLocale.getLocale());
|
function intl(key: string, params?: object): string {
|
||||||
|
|
||||||
function intlString(key: string, params?: object): string {
|
|
||||||
// TODO: tries lost language
|
// TODO: tries lost language
|
||||||
const str = data[key];
|
const str = data[key];
|
||||||
|
|
||||||
@ -115,30 +116,22 @@ export function createIntl(
|
|||||||
return `##intl@${key}##`;
|
return `##intl@${key}##`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return injectVars(str, params);
|
return injectVars(str, params, globalLocale.getLocale());
|
||||||
}
|
}
|
||||||
|
|
||||||
class Intl extends PureComponent<{ id: string; params?: object }> {
|
@observer
|
||||||
private dispose = globalLocale.onLocaleChange(locale => {
|
class IntlElement extends Component<{ id: string; params?: object }> {
|
||||||
if (lastLocale !== locale) {
|
|
||||||
useLocale(locale);
|
|
||||||
this.forceUpdate();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.dispose();
|
|
||||||
}
|
|
||||||
render() {
|
render() {
|
||||||
const { id, params } = this.props;
|
const { id, params } = this.props;
|
||||||
return intlString(id, params);
|
return intl(id, params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
intl(id: string, params?: object) {
|
intlNode(id: string, params?: object) {
|
||||||
return <Intl id={id} params={params} />;
|
return createElement(IntlElement, { id, params });
|
||||||
},
|
},
|
||||||
intlString,
|
intl,
|
||||||
getLocale() {
|
getLocale() {
|
||||||
return globalLocale.getLocale();
|
return globalLocale.getLocale();
|
||||||
},
|
},
|
||||||
@ -1,84 +0,0 @@
|
|||||||
import React, { createRef, PureComponent } from 'react';
|
|
||||||
|
|
||||||
import EditorContext from './context';
|
|
||||||
import { I18nFunction, PluginProps, PluginClass, Plugin } from './definitions';
|
|
||||||
import Editor from './editor';
|
|
||||||
import { acceptsRef, generateI18n, isEmpty, transformToPromise } from './utils';
|
|
||||||
|
|
||||||
export default function pluginFactory(Comp: PluginClass): React.ComponentType<PluginProps> {
|
|
||||||
class LowcodePlugin extends PureComponent<PluginProps> {
|
|
||||||
public static displayName = 'LowcodeEditorPlugin';
|
|
||||||
|
|
||||||
public static contextType = EditorContext;
|
|
||||||
|
|
||||||
public static init = Comp.init;
|
|
||||||
|
|
||||||
public ref: React.RefObject<React.ReactElement> & Plugin;
|
|
||||||
|
|
||||||
private editor: Editor;
|
|
||||||
|
|
||||||
private pluginKey: string;
|
|
||||||
|
|
||||||
private i18n: I18nFunction;
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
if (isEmpty(props.config) || !props.config.pluginKey) {
|
|
||||||
console.warn('lowcode editor plugin has wrong config');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { editor } = props;
|
|
||||||
this.ref = createRef<React.ReactElement>();
|
|
||||||
// 注册插件
|
|
||||||
this.editor = editor;
|
|
||||||
this.pluginKey = props.config.pluginKey;
|
|
||||||
const defaultProps = Comp.defaultProps || {};
|
|
||||||
const locale = this.editor.get('locale') || defaultProps.locale || 'zh-CN';
|
|
||||||
const editorMessages = this.editor.get('messages') || {};
|
|
||||||
const messages = editorMessages[this.pluginKey] || defaultProps.messages || {};
|
|
||||||
this.i18n = generateI18n(locale, messages);
|
|
||||||
|
|
||||||
editor.set('plugins', {
|
|
||||||
...editor.plugins,
|
|
||||||
[this.pluginKey]: this,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentWillUnmount(): void {
|
|
||||||
// 销毁插件
|
|
||||||
if (this.pluginKey && this.editor && this.editor.plugins) {
|
|
||||||
delete this.editor.plugins[this.pluginKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public open = (): Promise<any> => {
|
|
||||||
if (this.ref && this.ref.open && typeof this.ref.open === 'function') {
|
|
||||||
return transformToPromise(this.ref.open());
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
public close = (): Promise<any> => {
|
|
||||||
if (this.ref && this.ref.close && typeof this.ref.close === 'function') {
|
|
||||||
return transformToPromise(this.ref.close());
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
|
||||||
const { config } = this.props;
|
|
||||||
const props = {
|
|
||||||
i18n: this.i18n,
|
|
||||||
editor: this.editor,
|
|
||||||
config,
|
|
||||||
...config.pluginProps,
|
|
||||||
};
|
|
||||||
if (acceptsRef(Comp)) {
|
|
||||||
props.ref = this.ref;
|
|
||||||
}
|
|
||||||
return <Comp {...props} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return LowcodePlugin;
|
|
||||||
}
|
|
||||||
@ -1,273 +0,0 @@
|
|||||||
import IntlMessageFormat from 'intl-messageformat';
|
|
||||||
import keymaster from 'keymaster';
|
|
||||||
|
|
||||||
import _clone from 'lodash/cloneDeep';
|
|
||||||
import _debounce from 'lodash/debounce';
|
|
||||||
import _isEmpty from 'lodash/isEmpty';
|
|
||||||
import _deepEqual from 'lodash/isEqualWith';
|
|
||||||
import _pick from 'lodash/pick';
|
|
||||||
import _throttle from 'lodash/throttle';
|
|
||||||
|
|
||||||
import _serialize from 'serialize-javascript';
|
|
||||||
export { get, post, request } from './request';
|
|
||||||
import Editor from './editor';
|
|
||||||
import { EditorConfig, I18nFunction, I18nMessages, LocaleType, ShortCutsConfig } from './definitions';
|
|
||||||
|
|
||||||
export const pick = _pick;
|
|
||||||
export const deepEqual = _deepEqual;
|
|
||||||
export const clone = _clone;
|
|
||||||
export const isEmpty = _isEmpty;
|
|
||||||
export const throttle = _throttle;
|
|
||||||
export const debounce = _debounce;
|
|
||||||
|
|
||||||
export const serialize = _serialize;
|
|
||||||
|
|
||||||
const ENV = {
|
|
||||||
TBE: 'TBE',
|
|
||||||
WEBIDE: 'WEB-IDE',
|
|
||||||
VSCODE: 'VSCODE',
|
|
||||||
WEB: 'WEB',
|
|
||||||
};
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
sendIDEMessage?: (params: IDEMessageParams) => void;
|
|
||||||
goldlog?: {
|
|
||||||
record: (logKey: string, gmKey: string, goKey: string, method: 'POST' | 'GET') => (...args: any[]) => any;
|
|
||||||
};
|
|
||||||
is_theia?: boolean;
|
|
||||||
vscode?: boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDEMessageParams {
|
|
||||||
action: string;
|
|
||||||
data: {
|
|
||||||
logKey: string;
|
|
||||||
gmKey: string;
|
|
||||||
goKey: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 用于构造国际化字符串处理函数
|
|
||||||
*/
|
|
||||||
export function generateI18n(locale: LocaleType = 'zh-CN', messages: I18nMessages = {}): I18nFunction {
|
|
||||||
return (key: string, values): string => {
|
|
||||||
if (!messages || !messages[key]) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const formater = new IntlMessageFormat(messages[key], locale);
|
|
||||||
return formater.format(values);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 序列化参数
|
|
||||||
*/
|
|
||||||
export function serializeParams(obj: object): string {
|
|
||||||
if (typeof obj !== 'object') {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const res: string[] = [];
|
|
||||||
Object.entries(obj).forEach(([key, val]): void => {
|
|
||||||
if (val === null || val === undefined || val === '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof val === 'object') {
|
|
||||||
res.push(`${encodeURIComponent(key)}=${encodeURIComponent(JSON.stringify(val))}`);
|
|
||||||
} else {
|
|
||||||
res.push(`${encodeURIComponent(key)}=${encodeURIComponent(val)}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return res.join('&');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 黄金令箭埋点
|
|
||||||
* @param {String} gmKey 为黄金令箭业务类型
|
|
||||||
* @param {Object} params 参数
|
|
||||||
* @param {String} logKey 属性串
|
|
||||||
*/
|
|
||||||
export function goldlog(gmKey: string, params: object = {}, logKey: string = 'other'): void {
|
|
||||||
const sendIDEMessage = window.sendIDEMessage || window.parent.sendIDEMessage;
|
|
||||||
const goKey = serializeParams({
|
|
||||||
env: getEnv(),
|
|
||||||
...params,
|
|
||||||
});
|
|
||||||
if (sendIDEMessage) {
|
|
||||||
sendIDEMessage({
|
|
||||||
action: 'goldlog',
|
|
||||||
data: {
|
|
||||||
logKey: `/iceluna.core.${logKey}`,
|
|
||||||
gmKey,
|
|
||||||
goKey,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (window.goldlog) {
|
|
||||||
window.goldlog.record(`/iceluna.core.${logKey}`, gmKey, goKey, 'POST');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前编辑器环境
|
|
||||||
*/
|
|
||||||
export function getEnv(): string {
|
|
||||||
const userAgent = navigator.userAgent;
|
|
||||||
const isVscode = /Electron\//.test(userAgent);
|
|
||||||
if (isVscode) {
|
|
||||||
return ENV.VSCODE;
|
|
||||||
}
|
|
||||||
const isTheia = window.is_theia === true;
|
|
||||||
if (isTheia) {
|
|
||||||
return ENV.WEBIDE;
|
|
||||||
}
|
|
||||||
return ENV.WEB;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注册快捷键
|
|
||||||
export function registShortCuts(config: ShortCutsConfig, editor: Editor): void {
|
|
||||||
(config || []).forEach((item): void => {
|
|
||||||
keymaster(item.keyboard, (ev: Event): void => {
|
|
||||||
ev.preventDefault();
|
|
||||||
item.handler(editor, ev, keymaster);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消注册快捷
|
|
||||||
export function unRegistShortCuts(config: ShortCutsConfig): void {
|
|
||||||
(config || []).forEach((item): void => {
|
|
||||||
keymaster.unbind(item.keyboard);
|
|
||||||
});
|
|
||||||
if (window.parent.vscode) {
|
|
||||||
keymaster.unbind('command+c');
|
|
||||||
keymaster.unbind('command+v');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将函数返回结果转成promise形式,如果函数有返回值则根据返回值的bool类型判断是reject还是resolve,若函数无返回值默认执行resolve
|
|
||||||
*/
|
|
||||||
export function transformToPromise(input: any): Promise<{}> {
|
|
||||||
if (input instanceof Promise) {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject): void => {
|
|
||||||
if (input || input === undefined) {
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将数组类型转换为Map类型
|
|
||||||
*/
|
|
||||||
interface MapOf<T> {
|
|
||||||
[propName: string]: T;
|
|
||||||
}
|
|
||||||
export function transformArrayToMap<T>(arr: T[], key: string, overwrite: boolean = true): MapOf<T> {
|
|
||||||
if (isEmpty(arr) || !Array.isArray(arr)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const res = {};
|
|
||||||
arr.forEach((item): void => {
|
|
||||||
const curKey = item[key];
|
|
||||||
if (item[key] === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (res[curKey] && !overwrite) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res[curKey] = item;
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析url的查询参数
|
|
||||||
*/
|
|
||||||
interface Query {
|
|
||||||
[propName: string]: string;
|
|
||||||
}
|
|
||||||
export function parseSearch(search: string): Query {
|
|
||||||
if (!search || typeof search !== 'string') {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const str = search.replace(/^\?/, '');
|
|
||||||
const paramStr = str.split('&');
|
|
||||||
const res = {};
|
|
||||||
paramStr.forEach((item): void => {
|
|
||||||
const regRes = item.split('=');
|
|
||||||
if (regRes[0] && regRes[1]) {
|
|
||||||
res[regRes[0]] = decodeURIComponent(regRes[1]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function comboEditorConfig(defaultConfig: EditorConfig = {}, customConfig: EditorConfig): EditorConfig {
|
|
||||||
const { skeleton, theme, plugins, hooks, shortCuts, lifeCycles, constants, utils, i18n } = customConfig || {};
|
|
||||||
|
|
||||||
if (skeleton && skeleton.handler && typeof skeleton.handler === 'function') {
|
|
||||||
return skeleton.handler({
|
|
||||||
skeleton,
|
|
||||||
...defaultConfig,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultShortCuts = transformArrayToMap(defaultConfig.shortCuts || [], 'keyboard');
|
|
||||||
const customShortCuts = transformArrayToMap(shortCuts || [], 'keyboard');
|
|
||||||
const localeList = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP'];
|
|
||||||
const i18nConfig = {};
|
|
||||||
localeList.forEach((key): void => {
|
|
||||||
i18nConfig[key] = {
|
|
||||||
...(defaultConfig.i18n && defaultConfig.i18n[key]),
|
|
||||||
...(i18n && i18n[key]),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
skeleton,
|
|
||||||
theme: {
|
|
||||||
...defaultConfig.theme,
|
|
||||||
...theme,
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
...defaultConfig.plugins,
|
|
||||||
...plugins,
|
|
||||||
},
|
|
||||||
hooks: [...(defaultConfig.hooks || []), ...(hooks || [])],
|
|
||||||
shortCuts: Object.values({
|
|
||||||
...defaultShortCuts,
|
|
||||||
...customShortCuts,
|
|
||||||
}),
|
|
||||||
lifeCycles: {
|
|
||||||
...defaultConfig.lifeCycles,
|
|
||||||
...lifeCycles,
|
|
||||||
},
|
|
||||||
constants: {
|
|
||||||
...defaultConfig.constants,
|
|
||||||
...constants,
|
|
||||||
},
|
|
||||||
utils: [...(defaultConfig.utils || []), ...(utils || [])],
|
|
||||||
i18n: i18nConfig,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断当前组件是否能够设置ref
|
|
||||||
* @param {*} Comp 需要判断的组件
|
|
||||||
*/
|
|
||||||
export function acceptsRef(Comp: React.ReactNode): boolean {
|
|
||||||
const hasSymbol = typeof Symbol === 'function' && Symbol.for;
|
|
||||||
const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0;
|
|
||||||
if (!Comp || typeof Comp !== 'object' || isEmpty(Comp)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
(Comp.$$typeof && Comp.$$typeof === REACT_FORWARD_REF_TYPE) || (Comp.prototype && Comp.prototype.isReactComponent)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
11
packages/editor-core/src/utils/goldlog.ts
Normal file
11
packages/editor-core/src/utils/goldlog.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* 黄金令箭埋点
|
||||||
|
* @param {String} gmKey 为黄金令箭业务类型
|
||||||
|
* @param {Object} params 参数
|
||||||
|
* @param {String} logKey 属性串
|
||||||
|
*/
|
||||||
|
export function goldlog(gmKey: string, params: object = {}, logKey: string = 'other'): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
4
packages/editor-core/src/utils/index.ts
Normal file
4
packages/editor-core/src/utils/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './get-public-path';
|
||||||
|
export * from './goldlog';
|
||||||
|
export * from './obx';
|
||||||
|
export * from './request';
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import 'whatwg-fetch';
|
|
||||||
import Debug from 'debug';
|
import Debug from 'debug';
|
||||||
const debug = Debug('request');
|
const debug = Debug('request');
|
||||||
|
|
||||||
export function serialize(obj: object): string {
|
export function serialize(obj?: object): string {
|
||||||
|
if (!obj) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
const rst: string[] = [];
|
const rst: string[] = [];
|
||||||
Object.entries(obj || {}).forEach(([key, val]): void => {
|
Object.entries(obj || {}).forEach(([key, val]): void => {
|
||||||
if (val === null || val === undefined || val === '') return;
|
if (val === null || val === undefined || val === '') return;
|
||||||
@ -12,7 +14,7 @@ export function serialize(obj: object): string {
|
|||||||
return rst.join('&');
|
return rst.join('&');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildUrl(dataAPI: string, params: object): string {
|
export function buildUrl(dataAPI: string, params?: object): string {
|
||||||
const paramStr = serialize(params);
|
const paramStr = serialize(params);
|
||||||
if (paramStr) {
|
if (paramStr) {
|
||||||
return dataAPI.indexOf('?') > 0 ? `${dataAPI}&${paramStr}` : `${dataAPI}?${paramStr}`;
|
return dataAPI.indexOf('?') > 0 ? `${dataAPI}&${paramStr}` : `${dataAPI}?${paramStr}`;
|
||||||
@ -25,7 +27,7 @@ export function get(dataAPI: string, params?: object, headers?: object, otherPro
|
|||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
...headers,
|
...headers,
|
||||||
};
|
};
|
||||||
return request(buildUrl(dataAPI, params), 'GET', null, fetchHeaders, otherProps);
|
return request(buildUrl(dataAPI, params), 'GET', undefined, fetchHeaders, otherProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function post(dataAPI: string, params?: object, headers?: object, otherProps?: object): Promise<any> {
|
export function post(dataAPI: string, params?: object, headers?: object, otherProps?: object): Promise<any> {
|
||||||
@ -50,7 +52,7 @@ export function request(
|
|||||||
method: string = 'GET',
|
method: string = 'GET',
|
||||||
data?: object | string,
|
data?: object | string,
|
||||||
headers?: object,
|
headers?: object,
|
||||||
otherProps?: object,
|
otherProps?: any,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
return new Promise((resolve, reject): void => {
|
return new Promise((resolve, reject): void => {
|
||||||
if (otherProps && otherProps.timeout) {
|
if (otherProps && otherProps.timeout) {
|
||||||
@ -109,7 +111,7 @@ export function request(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((json: object): void => {
|
.then((json: any): void => {
|
||||||
if (json && json.__success !== false) {
|
if (json && json.__success !== false) {
|
||||||
resolve(json);
|
resolve(json);
|
||||||
} else {
|
} else {
|
||||||
@ -1,3 +1,3 @@
|
|||||||
|
// TODO move another place
|
||||||
export * from './tip';
|
export * from './tip';
|
||||||
export * from './title';
|
export * from './title';
|
||||||
export * from './svg-icon';
|
|
||||||
4
packages/editor-core/src/widgets/tip/index.ts
Normal file
4
packages/editor-core/src/widgets/tip/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import './style.less';
|
||||||
|
|
||||||
|
export * from './tip';
|
||||||
|
export * from './tip-container';
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import Tip from './tip';
|
import { TipItem } from './tip-item';
|
||||||
import tipHandler from './tip-handler';
|
import { tipHandler } from './tip-handler';
|
||||||
|
|
||||||
export default class TipContainer extends Component {
|
export class TipContainer extends Component {
|
||||||
shouldComponentUpdate() {
|
shouldComponentUpdate() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -29,7 +29,7 @@ export default class TipContainer extends Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="lc-tips-container">
|
<div className="lc-tips-container">
|
||||||
<Tip />
|
<TipItem />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1,54 +1,10 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { TipConfig } from '../../types';
|
import { TipConfig } from '@ali/lowcode-types';
|
||||||
|
|
||||||
export interface TipOptions extends TipConfig {
|
export interface TipOptions extends TipConfig {
|
||||||
target: HTMLElement;
|
target: HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findTip(target: HTMLElement | null): TipOptions | null {
|
|
||||||
if (!target) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// optimize deep finding on mouseover
|
|
||||||
let loopupLimit = 10;
|
|
||||||
while (target && loopupLimit-- > 0) {
|
|
||||||
// get tip from target node
|
|
||||||
if (target.dataset && target.dataset.tip) {
|
|
||||||
return {
|
|
||||||
children: target.dataset.tip,
|
|
||||||
direction: (target.dataset.direction || target.dataset.dir) as any,
|
|
||||||
theme: target.dataset.theme,
|
|
||||||
target,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// or get tip from child nodes
|
|
||||||
let child: HTMLElement | null = target.lastElementChild as HTMLElement;
|
|
||||||
|
|
||||||
while (child) {
|
|
||||||
if (child.dataset && child.dataset.role === 'tip') {
|
|
||||||
const tipId = child.dataset.tipId;
|
|
||||||
if (!tipId) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const tipProps = tipsMap.get(tipId);
|
|
||||||
if (!tipProps) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
...tipProps,
|
|
||||||
target,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
child = child.previousElementSibling as HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
target = target.parentNode as HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class TipHandler {
|
class TipHandler {
|
||||||
tip: TipOptions | null = null;
|
tip: TipOptions | null = null;
|
||||||
private showDelay: number | null = null;
|
private showDelay: number | null = null;
|
||||||
@ -60,7 +16,7 @@ class TipHandler {
|
|||||||
if (tip) {
|
if (tip) {
|
||||||
if (this.tip) {
|
if (this.tip) {
|
||||||
// the some target should return
|
// the some target should return
|
||||||
if (this.tip.target === tip.target) {
|
if ((this.tip as any).target === (tip as any).target) {
|
||||||
this.tip = tip;
|
this.tip = tip;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -127,13 +83,57 @@ class TipHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const tipHandler = new TipHandler();
|
||||||
|
|
||||||
|
function findTip(target: HTMLElement | null): TipOptions | null {
|
||||||
|
if (!target) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// optimize deep finding on mouseover
|
||||||
|
let loopupLimit = 10;
|
||||||
|
while (target && loopupLimit-- > 0) {
|
||||||
|
// get tip from target node
|
||||||
|
if (target.dataset && target.dataset.tip) {
|
||||||
|
return {
|
||||||
|
children: target.dataset.tip,
|
||||||
|
direction: (target.dataset.direction || target.dataset.dir) as any,
|
||||||
|
theme: target.dataset.theme,
|
||||||
|
target,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// or get tip from child nodes
|
||||||
|
let child: HTMLElement | null = target.lastElementChild as HTMLElement;
|
||||||
|
|
||||||
|
while (child) {
|
||||||
|
if (child.dataset && child.dataset.role === 'tip') {
|
||||||
|
const tipId = child.dataset.tipId;
|
||||||
|
if (!tipId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const tipProps = tipsMap.get(tipId);
|
||||||
|
if (!tipProps) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...tipProps,
|
||||||
|
target,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
child = child.previousElementSibling as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
target = target.parentNode as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const tipsMap = new Map<string, TipConfig>();
|
const tipsMap = new Map<string, TipConfig>();
|
||||||
export function saveTips(id: string, props: TipConfig | null) {
|
export function postTip(id: string, props: TipConfig | null) {
|
||||||
if (props) {
|
if (props) {
|
||||||
tipsMap.set(id, props);
|
tipsMap.set(id, props);
|
||||||
} else {
|
} else {
|
||||||
tipsMap.delete(id);
|
tipsMap.delete(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new TipHandler();
|
|
||||||
@ -1,10 +1,11 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { intl } from '@ali/lowcode-editor-core';
|
||||||
|
import { TipConfig } from '@ali/lowcode-types';
|
||||||
import { resolvePosition } from './utils';
|
import { resolvePosition } from './utils';
|
||||||
import tipHandler, { TipOptions } from './tip-handler';
|
import { tipHandler } from './tip-handler';
|
||||||
import { intl } from '../../intl';
|
|
||||||
|
|
||||||
export default class Tip extends Component {
|
export class TipItem extends Component {
|
||||||
private dispose?: () => void;
|
private dispose?: () => void;
|
||||||
constructor(props: any) {
|
constructor(props: any) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -101,7 +102,7 @@ export default class Tip extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const tip: TipOptions = tipHandler.tip || ({} as any);
|
const tip: TipConfig = tipHandler.tip || ({} as any);
|
||||||
const className = classNames('lc-tip', tip.className, tip && tip.theme ? `lc-theme-${tip.theme}` : null);
|
const className = classNames('lc-tip', tip.className, tip && tip.theme ? `lc-theme-${tip.theme}` : null);
|
||||||
|
|
||||||
this.originClassName = className;
|
this.originClassName = className;
|
||||||
17
packages/editor-core/src/widgets/tip/tip.tsx
Normal file
17
packages/editor-core/src/widgets/tip/tip.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Component } from 'react';
|
||||||
|
import { TipConfig } from '@ali/lowcode-types';
|
||||||
|
import { uniqueId } from '@ali/lowcode-utils';
|
||||||
|
import { postTip } from './tip-handler';
|
||||||
|
|
||||||
|
export class Tip extends Component<TipConfig> {
|
||||||
|
private id = uniqueId('tips$');
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
postTip(this.id, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
postTip(this.id, this.props);
|
||||||
|
return <meta data-role="tip" data-tip-id={this.id} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import { Component, isValidElement } from 'react';
|
import { Component, isValidElement } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import EmbedTip from '../tip/embed-tip';
|
import { createIcon } from '@ali/lowcode-utils';
|
||||||
import './title.less';
|
import { TitleContent, isI18nData } from '@ali/lowcode-types';
|
||||||
import { createIcon } from '../../utils';
|
|
||||||
import { TitleContent, isI18nData } from '../../types';
|
|
||||||
import { intl } from '../../intl';
|
import { intl } from '../../intl';
|
||||||
|
import { Tip } from '../tip';
|
||||||
|
import './title.less';
|
||||||
|
|
||||||
export class Title extends Component<{ title: TitleContent; className?: string; onClick?: () => void }> {
|
export class Title extends Component<{ title: TitleContent; className?: string; onClick?: () => void }> {
|
||||||
render() {
|
render() {
|
||||||
@ -20,14 +20,14 @@ export class Title extends Component<{ title: TitleContent; className?: string;
|
|||||||
|
|
||||||
let tip: any = null;
|
let tip: any = null;
|
||||||
if (title.tip) {
|
if (title.tip) {
|
||||||
if (isValidElement(title.tip) && title.tip.type === EmbedTip) {
|
if (isValidElement(title.tip) && title.tip.type === Tip) {
|
||||||
tip = title.tip;
|
tip = title.tip;
|
||||||
} else {
|
} else {
|
||||||
const tipProps =
|
const tipProps =
|
||||||
typeof title.tip === 'object' && !(isValidElement(title.tip) || isI18nData(title.tip))
|
typeof title.tip === 'object' && !(isValidElement(title.tip) || isI18nData(title.tip))
|
||||||
? title.tip
|
? title.tip
|
||||||
: { children: title.tip };
|
: { children: title.tip };
|
||||||
tip = <EmbedTip direction="top" theme="black" {...tipProps} />;
|
tip = <Tip direction="top" theme="black" {...tipProps} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3,7 +3,5 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "lib"
|
"outDir": "lib"
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["./src/"],
|
||||||
"./src/"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
59
packages/editor-skeleton/src/area.ts
Normal file
59
packages/editor-skeleton/src/area.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { obx, computed } from '@ali/lowcode-editor-core';
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,59 +0,0 @@
|
|||||||
.lowcode-left-plugin {
|
|
||||||
font-size: 20px;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 44px;
|
|
||||||
height: 44px;
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
color: $color-text1-3;
|
|
||||||
&.locked {
|
|
||||||
color: red !important;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
color: $color-brand1-6;
|
|
||||||
&:before {
|
|
||||||
content: attr(data-tooltip);
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
left: 45px;
|
|
||||||
top: 8px;
|
|
||||||
line-height: 18px;
|
|
||||||
font-size: 12px;
|
|
||||||
white-space: nowrap;
|
|
||||||
padding: 6px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: $balloon-normal-color-bg;
|
|
||||||
border: 1px solid $balloon-normal-color-border;
|
|
||||||
color: $color-text1-3;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
&:after {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
left: 40px;
|
|
||||||
top: 18px;
|
|
||||||
background: $balloon-normal-color-bg;
|
|
||||||
border-left: 1px solid $balloon-normal-color-border;
|
|
||||||
border-bottom: 1px solid $balloon-normal-color-border;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.active {
|
|
||||||
color: $color-brand1-9;
|
|
||||||
&.disabled {
|
|
||||||
color: $color-text1-1;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
color: $color-brand1-6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
color: $color-text1-1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,221 +0,0 @@
|
|||||||
import React, { PureComponent, Fragment } from 'react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { Balloon, Dialog, Icon, Badge } from '@alifd/next';
|
|
||||||
import Editor from '@ali/lowcode-editor-core';
|
|
||||||
import {
|
|
||||||
PluginConfig,
|
|
||||||
PluginClass,
|
|
||||||
} from '@ali/lowcode-editor-core/lib/definitions';
|
|
||||||
import './index.scss';
|
|
||||||
|
|
||||||
export interface LeftPluginProps {
|
|
||||||
active?: boolean;
|
|
||||||
config: PluginConfig;
|
|
||||||
disabled?: boolean;
|
|
||||||
editor: Editor;
|
|
||||||
locked?: boolean;
|
|
||||||
marked?: boolean;
|
|
||||||
onClick?: () => void;
|
|
||||||
pluginClass: PluginClass | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LeftPluginState {
|
|
||||||
dialogVisible: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class LeftPlugin extends PureComponent<
|
|
||||||
LeftPluginProps,
|
|
||||||
LeftPluginState
|
|
||||||
> {
|
|
||||||
static displayName = 'LowcodeLeftPlugin';
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
active: false,
|
|
||||||
config: {},
|
|
||||||
disabled: false,
|
|
||||||
marked: false,
|
|
||||||
locked: false,
|
|
||||||
onClick: (): void => {},
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
this.state = {
|
|
||||||
dialogVisible: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
|
||||||
const { config, editor } = this.props;
|
|
||||||
const pluginKey = config && config.pluginKey;
|
|
||||||
if (editor && pluginKey) {
|
|
||||||
editor.on(`${pluginKey}.dialog.show`, this.handleShow);
|
|
||||||
editor.on(`${pluginKey}.dialog.close`, this.handleClose);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
|
||||||
const { config, editor } = this.props;
|
|
||||||
const pluginKey = config && config.pluginKey;
|
|
||||||
if (editor && pluginKey) {
|
|
||||||
editor.off(`${pluginKey}.dialog.show`, this.handleShow);
|
|
||||||
editor.off(`${pluginKey}.dialog.close`, this.handleClose);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClose = (): void => {
|
|
||||||
const { config, editor } = this.props;
|
|
||||||
const pluginKey = config && config.pluginKey;
|
|
||||||
const plugin = editor.plugins && editor.plugins[pluginKey];
|
|
||||||
if (plugin) {
|
|
||||||
plugin.close().then((): void => {
|
|
||||||
this.setState({
|
|
||||||
dialogVisible: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleOpen = (): void => {
|
|
||||||
// todo 对话框类型的插件初始时拿不到插件实例
|
|
||||||
this.setState({
|
|
||||||
dialogVisible: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
handleShow = (): void => {
|
|
||||||
const { disabled, config, onClick, editor } = this.props;
|
|
||||||
const pluginKey = config && config.pluginKey;
|
|
||||||
if (disabled || !pluginKey) return;
|
|
||||||
this.handleOpen();
|
|
||||||
// 考虑到弹窗情况,延时发送消息
|
|
||||||
setTimeout((): void => {
|
|
||||||
editor.emit(`${pluginKey}.plugin.activate`);
|
|
||||||
}, 0);
|
|
||||||
if (onClick) {
|
|
||||||
onClick();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
renderIcon = (clickCallback): React.ReactNode => {
|
|
||||||
const { active, disabled, marked, locked, onClick, config } = this.props;
|
|
||||||
const { pluginKey, props } = config || {};
|
|
||||||
const { icon, title } = props || {};
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames('lowcode-left-plugin', pluginKey, {
|
|
||||||
active,
|
|
||||||
disabled,
|
|
||||||
locked,
|
|
||||||
})}
|
|
||||||
data-tooltip={title}
|
|
||||||
onClick={(): void => {
|
|
||||||
if (disabled) return;
|
|
||||||
// 考虑到弹窗情况,延时发送消息
|
|
||||||
clickCallback && clickCallback();
|
|
||||||
|
|
||||||
onClick && onClick();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{marked ? (
|
|
||||||
<Badge dot>
|
|
||||||
<Icon type={icon} size="small" />
|
|
||||||
</Badge>
|
|
||||||
) : (
|
|
||||||
<Icon type={icon} size="small" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
const {
|
|
||||||
marked,
|
|
||||||
locked,
|
|
||||||
active,
|
|
||||||
disabled,
|
|
||||||
config,
|
|
||||||
editor,
|
|
||||||
pluginClass: Comp,
|
|
||||||
} = this.props;
|
|
||||||
const { pluginKey, props, type, pluginProps } = config || {};
|
|
||||||
const { onClick, title } = props || {};
|
|
||||||
const { dialogVisible } = this.state;
|
|
||||||
if (!pluginKey || !type || !props) return null;
|
|
||||||
|
|
||||||
const node = Comp ? (
|
|
||||||
<Comp
|
|
||||||
editor={editor}
|
|
||||||
active={active}
|
|
||||||
locked={locked}
|
|
||||||
disabled={disabled}
|
|
||||||
config={config}
|
|
||||||
onClick={(): void => {
|
|
||||||
onClick && onClick.call(null, editor);
|
|
||||||
}}
|
|
||||||
{...pluginProps}
|
|
||||||
/>
|
|
||||||
) : null;
|
|
||||||
switch (type) {
|
|
||||||
case 'LinkIcon':
|
|
||||||
return (
|
|
||||||
<a {...(props.linkProps || {})}>
|
|
||||||
{this.renderIcon((): void => {
|
|
||||||
onClick && onClick.call(null, editor);
|
|
||||||
})}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
case 'Icon':
|
|
||||||
return this.renderIcon((): void => {
|
|
||||||
onClick && onClick.call(null, editor);
|
|
||||||
});
|
|
||||||
case 'DialogIcon':
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
{this.renderIcon((): void => {
|
|
||||||
onClick && onClick.call(null, editor);
|
|
||||||
this.handleOpen();
|
|
||||||
})}
|
|
||||||
<Dialog
|
|
||||||
onOk={(): void => {
|
|
||||||
editor.emit(`${pluginKey}.dialog.onOk`);
|
|
||||||
this.handleClose();
|
|
||||||
}}
|
|
||||||
onCancel={this.handleClose}
|
|
||||||
onClose={this.handleClose}
|
|
||||||
title={title}
|
|
||||||
style={{
|
|
||||||
width: 500,
|
|
||||||
...(props.dialogProps && props.dialogProps.style),
|
|
||||||
}}
|
|
||||||
{...(props.dialogProps || {})}
|
|
||||||
visible={dialogVisible}
|
|
||||||
>
|
|
||||||
{node}
|
|
||||||
</Dialog>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
case 'BalloonIcon':
|
|
||||||
return (
|
|
||||||
<Balloon
|
|
||||||
trigger={this.renderIcon((): void => {
|
|
||||||
onClick && onClick.call(null, editor);
|
|
||||||
})}
|
|
||||||
align="r"
|
|
||||||
triggerType={['click', 'hover']}
|
|
||||||
{...(props.balloonProps || {})}
|
|
||||||
>
|
|
||||||
{node}
|
|
||||||
</Balloon>
|
|
||||||
);
|
|
||||||
case 'PanelIcon':
|
|
||||||
return this.renderIcon((): void => {
|
|
||||||
onClick && onClick.call(null, editor);
|
|
||||||
});
|
|
||||||
case 'Custom':
|
|
||||||
return marked ? <Badge dot>{node}</Badge> : node;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
.lowcode-panel {
|
|
||||||
user-select: none;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
background: $card-background;
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
transform: translate3d(0, 0, 0);
|
|
||||||
height: 100%;
|
|
||||||
&.visible {
|
|
||||||
border-right: 1px solid $color-line1-1;
|
|
||||||
}
|
|
||||||
.drag-area {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
&.floatable {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
&.draggable {
|
|
||||||
.drag-area {
|
|
||||||
display: block;
|
|
||||||
width: 10px;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
cursor: col-resize;
|
|
||||||
z-index: 9999;
|
|
||||||
}
|
|
||||||
&.left {
|
|
||||||
.drag-area {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.right {
|
|
||||||
.drag-area {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.left {
|
|
||||||
&.floatable {
|
|
||||||
left: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.right {
|
|
||||||
&.floatable {
|
|
||||||
right: 48px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import './index.scss';
|
|
||||||
|
|
||||||
export interface PanelProps {
|
|
||||||
align: 'left' | 'right';
|
|
||||||
defaultWidth: number;
|
|
||||||
minWidth: number;
|
|
||||||
draggable: boolean;
|
|
||||||
floatable: boolean;
|
|
||||||
children: Plugin;
|
|
||||||
visible: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PanelState {
|
|
||||||
width: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Panel extends PureComponent<PanelProps, PanelState> {
|
|
||||||
static displayName = 'LowcodePanel';
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
align: 'left',
|
|
||||||
defaultWidth: 240,
|
|
||||||
minWidth: 100,
|
|
||||||
draggable: true,
|
|
||||||
floatable: false,
|
|
||||||
visible: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
width: props.defaultWidth,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
const { align, draggable, floatable, visible } = this.props;
|
|
||||||
const { width } = this.state;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames('lowcode-panel', align, {
|
|
||||||
draggable,
|
|
||||||
floatable,
|
|
||||||
visible,
|
|
||||||
})}
|
|
||||||
style={{
|
|
||||||
width,
|
|
||||||
display: visible ? '' : 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{this.props.children}
|
|
||||||
<div className="drag-area" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
.lowcode-top-icon {
|
|
||||||
display: inline-block;
|
|
||||||
width: 44px;
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 48px;
|
|
||||||
color: $color-text1-3;
|
|
||||||
position: relative;
|
|
||||||
&.disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
color: $color-text1-1;
|
|
||||||
&:hover {
|
|
||||||
color: $color-text1-1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.active {
|
|
||||||
color: $color-brand1-9;
|
|
||||||
&:hover {
|
|
||||||
color: $color-brand1-6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.locked {
|
|
||||||
color: red !important;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
color: $color-brand1-6;
|
|
||||||
&:before {
|
|
||||||
content: attr(data-tooltip);
|
|
||||||
display: block;
|
|
||||||
height: auto;
|
|
||||||
width: auto;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, 0);
|
|
||||||
bottom: -32px;
|
|
||||||
line-height: 18px;
|
|
||||||
font-size: 12px;
|
|
||||||
white-space: nowrap;
|
|
||||||
padding: 6px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: $balloon-normal-color-bg;
|
|
||||||
border: 1px solid $balloon-normal-color-border;
|
|
||||||
color: $color-text1-3;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
&:after {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, 0) rotate(45deg);
|
|
||||||
bottom: -5px;
|
|
||||||
background: $balloon-normal-color-bg;
|
|
||||||
border-left: 1px solid $balloon-normal-color-border;
|
|
||||||
border-top: 1px solid $balloon-normal-color-border;
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i.next-icon {
|
|
||||||
&:before {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
margin-right: 0;
|
|
||||||
line-height: 18px;
|
|
||||||
}
|
|
||||||
span {
|
|
||||||
display: block;
|
|
||||||
margin: 0px -5px 0;
|
|
||||||
line-height: 16px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 12px;
|
|
||||||
transform: scale(0.8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { Icon } from '@alifd/next';
|
|
||||||
|
|
||||||
import './index.scss';
|
|
||||||
|
|
||||||
export interface TopIconProps {
|
|
||||||
active?: boolean;
|
|
||||||
className?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
icon: string;
|
|
||||||
id?: string;
|
|
||||||
locked?: boolean;
|
|
||||||
marked?: boolean;
|
|
||||||
onClick?: () => void;
|
|
||||||
style?: React.CSSProperties;
|
|
||||||
title?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class TopIcon extends PureComponent<TopIconProps> {
|
|
||||||
static displayName = 'LowcodeTopIcon';
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
active: false,
|
|
||||||
className: '',
|
|
||||||
disabled: false,
|
|
||||||
icon: '',
|
|
||||||
id: '',
|
|
||||||
locked: false,
|
|
||||||
onClick: (): void => {},
|
|
||||||
style: {},
|
|
||||||
title: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
const {
|
|
||||||
active,
|
|
||||||
disabled,
|
|
||||||
icon,
|
|
||||||
locked,
|
|
||||||
title,
|
|
||||||
className,
|
|
||||||
id,
|
|
||||||
style,
|
|
||||||
onClick,
|
|
||||||
} = this.props;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames('lowcode-top-icon', className, {
|
|
||||||
active,
|
|
||||||
disabled,
|
|
||||||
locked,
|
|
||||||
})}
|
|
||||||
data-tooltip={title}
|
|
||||||
id={id}
|
|
||||||
style={style}
|
|
||||||
onClick={disabled ? undefined : onClick}
|
|
||||||
>
|
|
||||||
<Icon type={icon} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
.lowcode-top-addon {
|
|
||||||
}
|
|
||||||
@ -1,219 +0,0 @@
|
|||||||
import React, { PureComponent, Fragment } from 'react';
|
|
||||||
|
|
||||||
import { Balloon, Badge, Dialog } from '@alifd/next';
|
|
||||||
import Editor from '@ali/lowcode-editor-core';
|
|
||||||
import {
|
|
||||||
PluginConfig,
|
|
||||||
PluginClass,
|
|
||||||
} from '@ali/lowcode-editor-core/lib/definitions';
|
|
||||||
import TopIcon from '../TopIcon';
|
|
||||||
|
|
||||||
import './index.scss';
|
|
||||||
|
|
||||||
export interface TopPluginProps {
|
|
||||||
active?: boolean;
|
|
||||||
config: PluginConfig;
|
|
||||||
disabled?: boolean;
|
|
||||||
editor: Editor;
|
|
||||||
locked?: boolean;
|
|
||||||
marked?: boolean;
|
|
||||||
onClick?: () => void;
|
|
||||||
pluginClass: PluginClass | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TopPluginState {
|
|
||||||
dialogVisible: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class TopPlugin extends PureComponent<
|
|
||||||
TopPluginProps,
|
|
||||||
TopPluginState
|
|
||||||
> {
|
|
||||||
static displayName = 'LowcodeTopPlugin';
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
active: false,
|
|
||||||
config: {},
|
|
||||||
disabled: false,
|
|
||||||
marked: false,
|
|
||||||
locked: false,
|
|
||||||
onClick: (): void => {},
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
this.state = {
|
|
||||||
dialogVisible: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
|
||||||
const { config, editor } = this.props;
|
|
||||||
const pluginKey = config && config.pluginKey;
|
|
||||||
if (editor && pluginKey) {
|
|
||||||
editor.on(`${pluginKey}.dialog.show`, this.handleShow);
|
|
||||||
editor.on(`${pluginKey}.dialog.close`, this.handleClose);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount(): void {
|
|
||||||
const { config, editor } = this.props;
|
|
||||||
const pluginKey = config && config.pluginKey;
|
|
||||||
if (editor && pluginKey) {
|
|
||||||
editor.off(`${pluginKey}.dialog.show`, this.handleShow);
|
|
||||||
editor.off(`${pluginKey}.dialog.close`, this.handleClose);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleShow = (): void => {
|
|
||||||
const { disabled, config, onClick, editor } = this.props;
|
|
||||||
const pluginKey = config && config.pluginKey;
|
|
||||||
if (disabled || !pluginKey) return;
|
|
||||||
this.handleOpen();
|
|
||||||
// 考虑到弹窗情况,延时发送消息
|
|
||||||
setTimeout((): void => {
|
|
||||||
editor.emit(`${pluginKey}.plugin.activate`);
|
|
||||||
}, 0);
|
|
||||||
onClick && onClick();
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClose = (): void => {
|
|
||||||
const { config, editor } = this.props;
|
|
||||||
const pluginKey = config && config.pluginKey;
|
|
||||||
const plugin = editor.plugins && editor.plugins[pluginKey];
|
|
||||||
if (plugin) {
|
|
||||||
plugin.close().then((): void => {
|
|
||||||
this.setState({
|
|
||||||
dialogVisible: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleOpen = (): void => {
|
|
||||||
// todo dialog类型的插件初始时拿不动插件实例
|
|
||||||
this.setState({
|
|
||||||
dialogVisible: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
renderIcon = (clickCallback): React.ReactNode => {
|
|
||||||
const {
|
|
||||||
active,
|
|
||||||
disabled,
|
|
||||||
marked,
|
|
||||||
locked,
|
|
||||||
config,
|
|
||||||
onClick,
|
|
||||||
editor,
|
|
||||||
} = this.props;
|
|
||||||
const { pluginKey, props } = config || {};
|
|
||||||
const { icon, title } = props || {};
|
|
||||||
const node = (
|
|
||||||
<TopIcon
|
|
||||||
className={`lowcode-top-plugin ${pluginKey}`}
|
|
||||||
active={active}
|
|
||||||
disabled={disabled}
|
|
||||||
locked={locked}
|
|
||||||
icon={icon}
|
|
||||||
title={title}
|
|
||||||
onClick={(): void => {
|
|
||||||
if (disabled) return;
|
|
||||||
// 考虑到弹窗情况,延时发送消息
|
|
||||||
setTimeout((): void => {
|
|
||||||
editor.emit(`${pluginKey}.plugin.activate`);
|
|
||||||
}, 0);
|
|
||||||
clickCallback && clickCallback();
|
|
||||||
onClick && onClick();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
return marked ? <Badge dot>{node}</Badge> : node;
|
|
||||||
};
|
|
||||||
|
|
||||||
render(): React.ReactNode {
|
|
||||||
const {
|
|
||||||
active,
|
|
||||||
marked,
|
|
||||||
locked,
|
|
||||||
disabled,
|
|
||||||
config,
|
|
||||||
editor,
|
|
||||||
pluginClass: Comp,
|
|
||||||
} = this.props;
|
|
||||||
const { pluginKey, pluginProps, props, type } = config || {};
|
|
||||||
const { onClick, title } = props || {};
|
|
||||||
const { dialogVisible } = this.state;
|
|
||||||
if (!pluginKey || !type) return null;
|
|
||||||
const node = Comp ? (
|
|
||||||
<Comp
|
|
||||||
editor={editor}
|
|
||||||
active={active}
|
|
||||||
locked={locked}
|
|
||||||
disabled={disabled}
|
|
||||||
config={config}
|
|
||||||
onClick={(): void => {
|
|
||||||
onClick && onClick.call(null, editor);
|
|
||||||
}}
|
|
||||||
{...pluginProps}
|
|
||||||
/>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'LinkIcon':
|
|
||||||
return (
|
|
||||||
<a {...props.linkProps}>
|
|
||||||
{this.renderIcon((): void => {
|
|
||||||
onClick && onClick.call(null, editor);
|
|
||||||
})}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
case 'Icon':
|
|
||||||
return this.renderIcon((): void => {
|
|
||||||
onClick && onClick.call(null, editor);
|
|
||||||
});
|
|
||||||
case 'DialogIcon':
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
{this.renderIcon((): void => {
|
|
||||||
onClick && onClick.call(null, editor);
|
|
||||||
this.handleOpen();
|
|
||||||
})}
|
|
||||||
<Dialog
|
|
||||||
onOk={(): void => {
|
|
||||||
editor.emit(`${pluginKey}.dialog.onOk`);
|
|
||||||
this.handleClose();
|
|
||||||
}}
|
|
||||||
onCancel={this.handleClose}
|
|
||||||
onClose={this.handleClose}
|
|
||||||
title={title}
|
|
||||||
style={{
|
|
||||||
width: 500,
|
|
||||||
...(props.dialogProps && props.dialogProps.style),
|
|
||||||
}}
|
|
||||||
{...props.dialogProps}
|
|
||||||
visible={dialogVisible}
|
|
||||||
>
|
|
||||||
{node}
|
|
||||||
</Dialog>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
case 'BalloonIcon':
|
|
||||||
return (
|
|
||||||
<Balloon
|
|
||||||
trigger={this.renderIcon((): void => {
|
|
||||||
onClick && onClick.call(null, editor);
|
|
||||||
})}
|
|
||||||
triggerType={['click', 'hover']}
|
|
||||||
{...props.balloonProps}
|
|
||||||
>
|
|
||||||
{node}
|
|
||||||
</Balloon>
|
|
||||||
);
|
|
||||||
case 'Custom':
|
|
||||||
return marked ? <Badge dot>{node}</Badge> : node;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
* 拖拽排序有问题
|
||||||
|
* forceInline 有问题
|
||||||
|
* 部分改变不响应
|
||||||
|
* 样式还原
|
||||||
|
* autofocus
|
||||||
280
packages/editor-skeleton/src/components/array-setter/index.tsx
Normal file
280
packages/editor-skeleton/src/components/array-setter/index.tsx
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
import { Component, Fragment } from 'react';
|
||||||
|
import { Icon, Button, Message } from '@alifd/next';
|
||||||
|
import { Title } from '@ali/lowcode-editor-core';
|
||||||
|
import { SetterType, FieldConfig, SetterConfig } from '@ali/lowcode-types';
|
||||||
|
import { SettingField } from '@ali/lowcode-designer';
|
||||||
|
import { createSettingFieldView } from '../settings/settings-pane';
|
||||||
|
import { PopupContext, PopupPipe } from '../popup';
|
||||||
|
import Sortable from './sortable';
|
||||||
|
import './style.less';
|
||||||
|
|
||||||
|
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)} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,220 @@
|
|||||||
|
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;
|
||||||
103
packages/editor-skeleton/src/components/array-setter/style.less
Normal file
103
packages/editor-skeleton/src/components/array-setter/style.less
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
185
packages/editor-skeleton/src/components/field/fields.tsx
Normal file
185
packages/editor-skeleton/src/components/field/fields.tsx
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import { Component } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Icon } from '@alifd/next';
|
||||||
|
import { Title } from '@ali/lowcode-editor-core';
|
||||||
|
import { TitleContent } from '@ali/lowcode-types';
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
154
packages/editor-skeleton/src/components/field/index.less
Normal file
154
packages/editor-skeleton/src/components/field/index.less
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
packages/editor-skeleton/src/components/field/index.ts
Normal file
28
packages/editor-skeleton/src/components/field/index.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { ReactNode, createElement } from 'react';
|
||||||
|
import { TitleContent } from '@ali/lowcode-types';
|
||||||
|
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 };
|
||||||
262
packages/editor-skeleton/src/components/mixed-setter/index.tsx
Normal file
262
packages/editor-skeleton/src/components/mixed-setter/index.tsx
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
import React, { Component, isValidElement } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Dropdown, Menu } from '@alifd/next';
|
||||||
|
import {
|
||||||
|
SetterConfig,
|
||||||
|
CustomView,
|
||||||
|
DynamicProps,
|
||||||
|
DynamicSetter,
|
||||||
|
TitleContent,
|
||||||
|
isSetterConfig,
|
||||||
|
isDynamicSetter,
|
||||||
|
isI18nData,
|
||||||
|
} from '@ali/lowcode-types';
|
||||||
|
import {
|
||||||
|
getSetter,
|
||||||
|
getSettersMap,
|
||||||
|
computed,
|
||||||
|
obx,
|
||||||
|
Title,
|
||||||
|
createSetterContent,
|
||||||
|
observer,
|
||||||
|
shallowIntl,
|
||||||
|
Tip,
|
||||||
|
} from '@ali/lowcode-editor-core';
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
181
packages/editor-skeleton/src/components/object-setter/index.tsx
Normal file
181
packages/editor-skeleton/src/components/object-setter/index.tsx
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
import { Component, Fragment } from 'react';
|
||||||
|
import { Icon, Button } from '@alifd/next';
|
||||||
|
import { SetterType, FieldConfig } from '@ali/lowcode-types';
|
||||||
|
import { createSettingFieldView } from '../settings/settings-pane';
|
||||||
|
import { PopupContext, PopupPipe } from '../popup';
|
||||||
|
import { SettingField } from '@ali/lowcode-designer';
|
||||||
|
import './style.less';
|
||||||
|
import { Title } from '@ali/lowcode-editor-core';
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
.lc-setter-object-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
width: 100%;
|
||||||
|
.lc-setter-object-row-edit {
|
||||||
|
width: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.lc-setter-object-row-body {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
.lc-field {
|
||||||
|
padding: 0 !important;
|
||||||
|
.lc-field-body {
|
||||||
|
padding: 0 !important; margin: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> * {
|
||||||
|
flex: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
margin-left: 2px;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
150
packages/editor-skeleton/src/components/popup/index.tsx
Normal file
150
packages/editor-skeleton/src/components/popup/index.tsx
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
import { createContext, ReactNode, Component, PureComponent } from 'react';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
import { Balloon } from '@alifd/next';
|
||||||
|
import { uniqueId } from '@ali/lowcode-utils';
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user