mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-02-20 23:50:28 +00:00
add toolbar&intl
This commit is contained in:
parent
897d01ebff
commit
f0a023df6d
@ -85,7 +85,7 @@ function processDetail({ target, detail, document }: Location): InsertionData {
|
||||
if (!instances) {
|
||||
return {};
|
||||
}
|
||||
const edge = sim.computeComponentInstanceRect(instances[0]);
|
||||
const edge = sim.computeComponentInstanceRect(instances[0], target.componentMeta.rectSelector);
|
||||
return edge ? { edge, insertType: 'cover', coverRect: edge } : {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ export class OutlineHovering extends Component {
|
||||
scale={this.scale}
|
||||
scrollX={this.scrollX}
|
||||
scrollY={this.scrollY}
|
||||
rect={host.computeComponentInstanceRect(instances[0])}
|
||||
rect={host.computeComponentInstanceRect(instances[0], current.componentMeta.rectSelector)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -101,7 +101,7 @@ export class OutlineHovering extends Component {
|
||||
scale={this.scale}
|
||||
scrollX={this.scrollX}
|
||||
scrollY={this.scrollY}
|
||||
rect={host.computeComponentInstanceRect(inst)}
|
||||
rect={host.computeComponentInstanceRect(inst, current.componentMeta.rectSelector)}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
|
||||
@ -1,4 +1,13 @@
|
||||
import { Component, Fragment } from 'react';
|
||||
import {
|
||||
Component,
|
||||
Fragment,
|
||||
ReactNodeArray,
|
||||
isValidElement,
|
||||
cloneElement,
|
||||
createElement,
|
||||
ReactNode,
|
||||
ComponentType,
|
||||
} from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { observer } from '@recore/obx-react';
|
||||
import { SimulatorContext } from '../context';
|
||||
@ -6,6 +15,8 @@ import { SimulatorHost } from '../host';
|
||||
import { computed } from '@recore/obx';
|
||||
import OffsetObserver from '../../../../designer/helper/offset-observer';
|
||||
import Node from '../../../../designer/document/node/node';
|
||||
import { isContentObject, ContentObject } from '../../../../designer/component-meta';
|
||||
import { createIcon, EmbedTip, isReactComponent } from '../../../../../../globals';
|
||||
|
||||
@observer
|
||||
export class OutlineSelectingInstance extends Component<{
|
||||
@ -38,12 +49,89 @@ export class OutlineSelectingInstance extends Component<{
|
||||
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<a className="lc-outlines-title">{observed.nodeInstance.node.title}</a>
|
||||
{!dragging && <Toolbar observed={observed} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
class Toolbar extends Component<{ observed: OffsetObserver }> {
|
||||
shouldComponentUpdate() {
|
||||
return false;
|
||||
}
|
||||
render() {
|
||||
const { observed } = this.props;
|
||||
const { height, width } = observed.viewport;
|
||||
const BAR_HEIGHT = 20;
|
||||
const MARGIN = 1;
|
||||
const BORDER = 2;
|
||||
const SPACE_HEIGHT = BAR_HEIGHT + MARGIN + BORDER;
|
||||
let style: any;
|
||||
if (observed.top > SPACE_HEIGHT) {
|
||||
style = {
|
||||
right: Math.max(-BORDER, observed.right - width - BORDER),
|
||||
top: -SPACE_HEIGHT,
|
||||
height: BAR_HEIGHT,
|
||||
};
|
||||
} else if (observed.bottom + SPACE_HEIGHT < height) {
|
||||
style = {
|
||||
bottom: -SPACE_HEIGHT,
|
||||
height: BAR_HEIGHT,
|
||||
right: Math.max(-BORDER, observed.right - width - BORDER),
|
||||
};
|
||||
} else {
|
||||
style = {
|
||||
height: BAR_HEIGHT,
|
||||
top: Math.max(MARGIN, MARGIN - observed.top),
|
||||
right: Math.max(MARGIN, MARGIN + observed.right - width),
|
||||
};
|
||||
}
|
||||
const { node } = observed;
|
||||
const actions: ReactNodeArray = [];
|
||||
node.componentMeta.availableActions.forEach(action => {
|
||||
const { important, condition, content, name } = action;
|
||||
if (node.isSlotRoot && (name === 'copy' || name === 'remove')) {
|
||||
// FIXME: need this?
|
||||
return;
|
||||
}
|
||||
if (important && (typeof condition === 'function' ? condition(node) : condition !== false)) {
|
||||
actions.push(createAction(content, name, node));
|
||||
}
|
||||
});
|
||||
return (
|
||||
<div className="lc-outlines-actions" style={style}>
|
||||
{actions}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function createAction(content: ReactNode | ComponentType<any> | ContentObject, key: string, node: Node) {
|
||||
if (isValidElement(content)) {
|
||||
return cloneElement(content, { key, node });
|
||||
}
|
||||
if (isReactComponent(content)) {
|
||||
return createElement(content, { key, node });
|
||||
}
|
||||
if (isContentObject(content)) {
|
||||
const { action, description, icon } = content;
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className="lc-outlines-action"
|
||||
onClick={() => {
|
||||
action && action(node);
|
||||
}}
|
||||
>
|
||||
{icon && createIcon(icon)}
|
||||
<EmbedTip>{description}</EmbedTip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class OutlineSelectingForNode extends Component<{ node: Node }> {
|
||||
static contextType = SimulatorContext;
|
||||
|
||||
@ -18,6 +18,32 @@
|
||||
transform: translateY(-100%);
|
||||
font-weight: lighter;
|
||||
}
|
||||
& > &-actions {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: stretch;
|
||||
justify-content: flex-end;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
&-action {
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-brand, #006cff);
|
||||
opacity: 1;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
&:hover {
|
||||
background: var(--color-brand-light, #006cff);
|
||||
}
|
||||
}
|
||||
|
||||
&&-hovering {
|
||||
z-index: 1;
|
||||
|
||||
@ -33,6 +33,7 @@ import { ComponentMetadata } from '../../../designer/component-meta';
|
||||
import { ReactInstance } from 'react';
|
||||
import { isRootNode } from '../../../designer/document/node/root-node';
|
||||
import { parseProps } from '../utils/parse-props';
|
||||
import { isElement } from '../../../utils/is-element';
|
||||
|
||||
export interface LibraryItem {
|
||||
package: string;
|
||||
@ -450,13 +451,13 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
||||
if (!instances) {
|
||||
return null;
|
||||
}
|
||||
return this.computeComponentInstanceRect(instances[0]);
|
||||
return this.computeComponentInstanceRect(instances[0], node.componentMeta.rectSelector);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ISimulator
|
||||
*/
|
||||
computeComponentInstanceRect(instance: ReactInstance): Rect | null {
|
||||
computeComponentInstanceRect(instance: ReactInstance, selector?: string): Rect | null {
|
||||
const renderer = this.renderer!;
|
||||
const elements = renderer.findDOMNodes(instance);
|
||||
if (!elements) {
|
||||
@ -466,20 +467,27 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
||||
let rects: DOMRect[] | undefined;
|
||||
let last: { x: number; y: number; r: number; b: number } | undefined;
|
||||
let computed = false;
|
||||
const elems = elements.slice();
|
||||
const commonParent: Element | null = null;
|
||||
const elems = selector
|
||||
? elements
|
||||
.map(elem => {
|
||||
if (isElement(elem)) {
|
||||
// TODO: if has selector use exact match
|
||||
if (elem.matches(selector)) {
|
||||
return elem;
|
||||
}
|
||||
|
||||
return elem.querySelector(selector);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean)
|
||||
: elements.slice();
|
||||
while (true) {
|
||||
if (!rects || rects.length < 1) {
|
||||
const elem = elems.pop();
|
||||
if (!elem) {
|
||||
break;
|
||||
}
|
||||
/*
|
||||
if (!commonParent) {
|
||||
commonParent = elem.parentElement;
|
||||
} else if (elem.parentElement !== commonParent) {
|
||||
continue;
|
||||
}*/
|
||||
rects = renderer.getClientRects(elem);
|
||||
}
|
||||
const rect = rects.pop();
|
||||
@ -716,7 +724,10 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
||||
|
||||
const targetInstance = e.targetInstance as ReactInstance;
|
||||
const parentInstance = this.getClosestNodeInstance(targetInstance, target.id);
|
||||
const edge = this.computeComponentInstanceRect(parentInstance?.instance as any);
|
||||
const edge = this.computeComponentInstanceRect(
|
||||
parentInstance?.instance as any,
|
||||
parentInstance?.node?.componentMeta.rectSelector,
|
||||
);
|
||||
|
||||
if (!edge) {
|
||||
return null;
|
||||
@ -755,7 +766,7 @@ export class SimulatorHost implements ISimulator<SimulatorProps> {
|
||||
? instances.find(inst => this.getClosestNodeInstance(inst, target.id)?.instance === targetInstance)
|
||||
: instances[0]
|
||||
: null;
|
||||
const rect = inst ? this.computeComponentInstanceRect(inst) : null;
|
||||
const rect = inst ? this.computeComponentInstanceRect(inst, node.componentMeta.rectSelector) : null;
|
||||
|
||||
if (!rect) {
|
||||
continue;
|
||||
|
||||
@ -0,0 +1,412 @@
|
||||
import { ReactElement, createElement, ReactType } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const supportedEvents = [
|
||||
// MouseEvents
|
||||
{
|
||||
name: 'onClick',
|
||||
description: '点击时',
|
||||
},
|
||||
{
|
||||
name: 'onDoubleClick',
|
||||
description: '双击时',
|
||||
},
|
||||
{
|
||||
name: 'onMouseDown',
|
||||
description: '鼠标按下',
|
||||
},
|
||||
{
|
||||
name: 'onMouseEnter',
|
||||
description: '鼠标进入',
|
||||
},
|
||||
{
|
||||
name: 'onMouseMove',
|
||||
description: '鼠标移动',
|
||||
},
|
||||
{
|
||||
name: 'onMouseOut',
|
||||
description: '鼠标移出',
|
||||
},
|
||||
{
|
||||
name: 'onMouseOver',
|
||||
description: '鼠标悬停',
|
||||
},
|
||||
{
|
||||
name: 'onMouseUp',
|
||||
description: '鼠标松开',
|
||||
},
|
||||
// Focus Events
|
||||
{
|
||||
name: 'onFocus',
|
||||
description: '获得焦点',
|
||||
snippet: '',
|
||||
},
|
||||
{
|
||||
name: 'onBlur',
|
||||
description: '失去焦点',
|
||||
snippet: '',
|
||||
},
|
||||
// Form Events
|
||||
{
|
||||
name: 'onChange',
|
||||
description: '值改变时',
|
||||
snippet: '',
|
||||
},
|
||||
{
|
||||
name: 'onSelect',
|
||||
description: '选择',
|
||||
},
|
||||
{
|
||||
name: 'onInput',
|
||||
description: '输入',
|
||||
snippet: '',
|
||||
},
|
||||
{
|
||||
name: 'onReset',
|
||||
description: '重置',
|
||||
snippet: '',
|
||||
},
|
||||
{
|
||||
name: 'onSubmit',
|
||||
description: '提交',
|
||||
snippet: '',
|
||||
},
|
||||
// Clipboard Events
|
||||
{
|
||||
name: 'onCopy',
|
||||
description: '复制',
|
||||
snippet: '',
|
||||
},
|
||||
{
|
||||
name: 'onCut',
|
||||
description: '剪切',
|
||||
snippet: '',
|
||||
},
|
||||
{
|
||||
name: 'onPaste',
|
||||
description: '粘贴',
|
||||
snippet: '',
|
||||
},
|
||||
|
||||
// Keyboard Events
|
||||
{
|
||||
name: 'onKeyDown',
|
||||
description: '键盘按下',
|
||||
snippet: '',
|
||||
},
|
||||
{
|
||||
name: 'onKeyPress',
|
||||
description: '键盘按下并释放',
|
||||
snippet: '',
|
||||
},
|
||||
{
|
||||
name: 'onKeyUp',
|
||||
description: '键盘松开',
|
||||
snippet: '',
|
||||
},
|
||||
// Touch Events
|
||||
{
|
||||
name: 'onTouchCancel',
|
||||
description: '触摸退出',
|
||||
snippet: '',
|
||||
},
|
||||
{
|
||||
name: 'onTouchEnd',
|
||||
description: '触摸结束',
|
||||
snippet: '',
|
||||
},
|
||||
{
|
||||
name: 'onTouchMove',
|
||||
description: '触摸移动',
|
||||
snippet: '',
|
||||
},
|
||||
{
|
||||
name: 'onTouchStart',
|
||||
description: '触摸开始',
|
||||
snippet: '',
|
||||
},
|
||||
// UI Events
|
||||
{
|
||||
name: 'onScroll',
|
||||
description: '滚动',
|
||||
snippet: '',
|
||||
},
|
||||
{
|
||||
name: 'onLoad',
|
||||
description: '加载完毕',
|
||||
snippet: '',
|
||||
},
|
||||
{
|
||||
name: 'onWheel',
|
||||
description: '滚轮事件',
|
||||
snippet: '',
|
||||
},
|
||||
// Animation Events
|
||||
{
|
||||
name: 'onAnimationStart',
|
||||
description: '动画开始',
|
||||
},
|
||||
{
|
||||
name: 'onAnimationEnd',
|
||||
description: '动画结束',
|
||||
},
|
||||
];
|
||||
|
||||
const builtinComponents = new Map<string, (props: any) => ReactElement>();
|
||||
function getBlockElement(tag: string): (props: any) => ReactElement {
|
||||
if (builtinComponents.has(tag)) {
|
||||
return builtinComponents.get(tag)!;
|
||||
}
|
||||
const mock = ({ className, children, ...rest }: any = {}) => {
|
||||
const props = {
|
||||
...rest,
|
||||
className: classNames('lc-block-container', className),
|
||||
};
|
||||
return createElement(tag, props, children);
|
||||
};
|
||||
|
||||
mock.metadata = {
|
||||
componentName: tag,
|
||||
// selfControlled: true,
|
||||
configure: {
|
||||
props: [],
|
||||
events: {
|
||||
supportedEvents,
|
||||
},
|
||||
styles: {
|
||||
supportClassName: true,
|
||||
supportInlineStyle: true,
|
||||
},
|
||||
component: {
|
||||
...metasMap[tag],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
builtinComponents.set(tag, mock);
|
||||
return mock;
|
||||
}
|
||||
|
||||
const HTMLBlock = [
|
||||
'div',
|
||||
'p',
|
||||
'article',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'aside',
|
||||
'blockquote',
|
||||
'footer',
|
||||
'form',
|
||||
'header',
|
||||
'table',
|
||||
'tbody',
|
||||
'section',
|
||||
'ul',
|
||||
'li',
|
||||
];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const HTMLInlineBlock = ['a', 'b', 'span', 'em'];
|
||||
export function getIntrinsicMock(tag: string): ReactType {
|
||||
if (HTMLBlock.indexOf(tag) > -1) {
|
||||
return getBlockElement(tag);
|
||||
}
|
||||
|
||||
return tag as any;
|
||||
}
|
||||
|
||||
const metasMap: any = {
|
||||
div: {
|
||||
isContainer: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: 'p',
|
||||
},
|
||||
},
|
||||
ul: {
|
||||
isContainer: true,
|
||||
nesting: {
|
||||
childWhitelist: 'li',
|
||||
},
|
||||
},
|
||||
p: {
|
||||
isContainer: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: 'button,p',
|
||||
},
|
||||
},
|
||||
li: {
|
||||
isContainer: true,
|
||||
nesting: {
|
||||
parentWhitelist: 'ui,ol',
|
||||
},
|
||||
},
|
||||
span: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
},
|
||||
a: {
|
||||
isContainer: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: 'a',
|
||||
},
|
||||
},
|
||||
b: {
|
||||
isContainer: true,
|
||||
},
|
||||
strong: {
|
||||
isContainer: true,
|
||||
},
|
||||
em: {
|
||||
isContainer: true,
|
||||
},
|
||||
i: {
|
||||
isContainer: true,
|
||||
},
|
||||
form: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'form,button',
|
||||
},
|
||||
},
|
||||
table: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button',
|
||||
},
|
||||
},
|
||||
caption: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button',
|
||||
},
|
||||
},
|
||||
select: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button',
|
||||
},
|
||||
},
|
||||
button: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button',
|
||||
},
|
||||
},
|
||||
input: {
|
||||
isContainer: false,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button,h1,h2,h3,h4,h5,h6',
|
||||
},
|
||||
},
|
||||
textarea: {
|
||||
isContainer: false,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button',
|
||||
},
|
||||
},
|
||||
image: {
|
||||
isContainer: false,
|
||||
},
|
||||
canvas: {
|
||||
isContainer: false,
|
||||
},
|
||||
br: {
|
||||
isContainer: false,
|
||||
},
|
||||
h1: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'p,h1,h2,h3,h4,h5,h6,button',
|
||||
},
|
||||
},
|
||||
h2: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'p,h1,h2,h3,h4,h5,h6,button',
|
||||
},
|
||||
},
|
||||
h3: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'p,h1,h2,h3,h4,h5,h6,button',
|
||||
},
|
||||
},
|
||||
h4: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'p,h1,h2,h3,h4,h5,h6,button',
|
||||
},
|
||||
},
|
||||
h5: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'p,h1,h2,h3,h4,h5,h6,button',
|
||||
},
|
||||
},
|
||||
h6: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'p,h1,h2,h3,h4,h5,h6,button',
|
||||
},
|
||||
},
|
||||
article: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button',
|
||||
},
|
||||
},
|
||||
aside: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button',
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button',
|
||||
},
|
||||
},
|
||||
header: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button',
|
||||
},
|
||||
},
|
||||
blockquote: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button',
|
||||
},
|
||||
},
|
||||
address: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button',
|
||||
},
|
||||
},
|
||||
section: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'p,h1,h2,h3,h4,h5,h6,button',
|
||||
},
|
||||
},
|
||||
summary: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button',
|
||||
},
|
||||
},
|
||||
nav: {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
ancestorBlacklist: 'button',
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -85,7 +85,7 @@ export class SimulatorRenderer {
|
||||
}
|
||||
|
||||
@computed get designMode(): any {
|
||||
return 'border';
|
||||
return 'preview';
|
||||
}
|
||||
@obx.ref private _componentsMap = {};
|
||||
@computed get componentsMap(): any {
|
||||
|
||||
@ -1,284 +0,0 @@
|
||||
import { ReactElement, createElement, ReactType } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const mocksCache = new Map<string, (props: any) => ReactElement>();
|
||||
// endpoint element: input,select,video,audio,canvas,textarea
|
||||
//
|
||||
function getBlockElement(tag: string): (props: any) => ReactElement {
|
||||
if (mocksCache.has(tag)) {
|
||||
return mocksCache.get(tag)!;
|
||||
}
|
||||
const mock = ({ className, children, ...rest }: any = {}) => {
|
||||
const props = {
|
||||
...rest,
|
||||
className: classNames('my-intrinsic-container', className),
|
||||
};
|
||||
return createElement(tag, props, children);
|
||||
};
|
||||
|
||||
mock.prototypeConfig = {
|
||||
uri: `@html:${tag}`,
|
||||
selfControlled: true,
|
||||
...(prototypeMap as any)[tag],
|
||||
};
|
||||
|
||||
mocksCache.set(tag, mock);
|
||||
return mock;
|
||||
}
|
||||
|
||||
const HTMLBlock = [
|
||||
'div',
|
||||
'p',
|
||||
'article',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'aside',
|
||||
'blockquote',
|
||||
'footer',
|
||||
'form',
|
||||
'header',
|
||||
'table',
|
||||
'tbody',
|
||||
'section',
|
||||
'ul',
|
||||
'li',
|
||||
'span',
|
||||
];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const HTMLInlineBlock = ['a', 'b', 'span', 'em'];
|
||||
export function getIntrinsicMock(tag: string): ReactType {
|
||||
if (HTMLBlock.indexOf(tag) > -1) {
|
||||
return getBlockElement(tag);
|
||||
}
|
||||
|
||||
return tag as any;
|
||||
}
|
||||
|
||||
const prototypeMap = {
|
||||
div: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: 'p',
|
||||
},
|
||||
},
|
||||
ul: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
childWhitelist: 'li',
|
||||
},
|
||||
},
|
||||
p: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: 'button,p',
|
||||
},
|
||||
},
|
||||
li: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
parentWhitelist: 'ui,ol',
|
||||
},
|
||||
},
|
||||
span: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
},
|
||||
a: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!a',
|
||||
},
|
||||
},
|
||||
b: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
},
|
||||
strong: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
},
|
||||
em: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
},
|
||||
i: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
},
|
||||
form: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!form,!button',
|
||||
},
|
||||
},
|
||||
table: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button',
|
||||
},
|
||||
},
|
||||
caption: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button',
|
||||
},
|
||||
},
|
||||
select: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button',
|
||||
},
|
||||
},
|
||||
button: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button',
|
||||
},
|
||||
},
|
||||
input: {
|
||||
isContainer: false,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button,!h1,!h2,!h3,!h4,!h5',
|
||||
},
|
||||
},
|
||||
textarea: {
|
||||
isContainer: false,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button',
|
||||
},
|
||||
},
|
||||
image: {
|
||||
isContainer: false,
|
||||
selfControlled: true,
|
||||
},
|
||||
canvas: {
|
||||
isContainer: false,
|
||||
selfControlled: true,
|
||||
},
|
||||
br: {
|
||||
isContainer: false,
|
||||
selfControlled: true,
|
||||
},
|
||||
h1: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!p,!h1,!h2,!h3,!h4,!h5,!h6,!button',
|
||||
},
|
||||
},
|
||||
h2: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!p,!h1,!h2,!h3,!h4,!h5,!h6,!button',
|
||||
},
|
||||
},
|
||||
h3: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!p,!h1,!h2,!h3,!h4,!h5,!h6,!button',
|
||||
},
|
||||
},
|
||||
h4: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!p,!h1,!h2,!h3,!h4,!h5,!h6,!button',
|
||||
},
|
||||
},
|
||||
h5: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!p,!h1,!h2,!h3,!h4,!h5,!h6,!button',
|
||||
},
|
||||
},
|
||||
h6: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!p,!h1,!h2,!h3,!h4,!h5,!h6,!button',
|
||||
},
|
||||
},
|
||||
article: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button',
|
||||
},
|
||||
},
|
||||
aside: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button',
|
||||
},
|
||||
},
|
||||
footer: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button',
|
||||
},
|
||||
},
|
||||
header: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button',
|
||||
},
|
||||
},
|
||||
blockquote: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button',
|
||||
},
|
||||
},
|
||||
address: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button',
|
||||
},
|
||||
},
|
||||
section: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!p,!h1,!h2,!h3,!h4,!h5,!h6,!button',
|
||||
},
|
||||
},
|
||||
summary: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button',
|
||||
},
|
||||
},
|
||||
nav: {
|
||||
isContainer: true,
|
||||
selfControlled: true,
|
||||
nesting: {
|
||||
ancestorBlacklist: '!button',
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -1,7 +1,11 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { ReactNode, ReactElement, ComponentType, createElement } from 'react';
|
||||
import Node, { NodeParent } from './document/node/node';
|
||||
import { NodeData, NodeSchema } from './schema';
|
||||
import { PropConfig } from './prop-config';
|
||||
import Designer from './designer';
|
||||
import { Remove, Clone } from '../../../globals';
|
||||
import { computed } from '@recore/obx';
|
||||
import { intl } from '../locale';
|
||||
|
||||
export interface NestingRule {
|
||||
childWhitelist?: string[];
|
||||
@ -17,9 +21,39 @@ export interface Configure {
|
||||
isModal?: boolean;
|
||||
descriptor?: string;
|
||||
nestingRule?: NestingRule;
|
||||
rectSelector?: string;
|
||||
// copy,move,delete
|
||||
disableBehaviors?: string[];
|
||||
actions?: ComponentAction[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface ContentObject {
|
||||
// 图标
|
||||
icon?: string | ComponentType<any> | ReactElement;
|
||||
// 描述
|
||||
description?: string;
|
||||
// 执行动作
|
||||
action?: (node: Node) => void;
|
||||
}
|
||||
|
||||
export interface ComponentAction {
|
||||
// behaviorName
|
||||
name: string;
|
||||
// 菜单名称
|
||||
content: string | ReactNode | ContentObject;
|
||||
// 子集
|
||||
items?: ComponentAction[];
|
||||
// 不显示
|
||||
condition?: boolean | ((node: Node) => boolean);
|
||||
// 显示在工具条上
|
||||
important?: boolean;
|
||||
}
|
||||
|
||||
export function isContentObject(obj: any): obj is ContentObject {
|
||||
return obj && typeof obj === 'object';
|
||||
}
|
||||
|
||||
export interface ComponentMetadata {
|
||||
componentName: string;
|
||||
/**
|
||||
@ -52,7 +86,7 @@ export interface ComponentMetadata {
|
||||
}
|
||||
|
||||
interface TransformedComponentMetadata extends ComponentMetadata {
|
||||
configure?: Configure & {
|
||||
configure: Configure & {
|
||||
combined?: any[];
|
||||
};
|
||||
}
|
||||
@ -102,9 +136,12 @@ function npmToURI(npm: {
|
||||
return uri;
|
||||
}
|
||||
|
||||
export type MetadataTransducer = (prev: ComponentMetadata) => TransformedComponentMetadata;
|
||||
export type MetadataTransducer = (prev: TransformedComponentMetadata) => TransformedComponentMetadata;
|
||||
const metadataTransducers: MetadataTransducer[] = [];
|
||||
|
||||
// propsParser
|
||||
//
|
||||
|
||||
export function registerMetadataTransducer(transducer: MetadataTransducer) {
|
||||
metadataTransducers.push(transducer);
|
||||
}
|
||||
@ -128,8 +165,12 @@ export class ComponentMeta {
|
||||
return this._isModal!;
|
||||
}
|
||||
private _descriptor?: string;
|
||||
get descriptor(): string {
|
||||
return this._descriptor!;
|
||||
get descriptor(): string | undefined {
|
||||
return this._descriptor;
|
||||
}
|
||||
private _rectSelector?: string;
|
||||
get rectSelector(): string | undefined {
|
||||
return this._rectSelector;
|
||||
}
|
||||
private _acceptable?: boolean;
|
||||
get acceptable(): boolean {
|
||||
@ -152,7 +193,7 @@ export class ComponentMeta {
|
||||
return this._metadata.icon;
|
||||
}
|
||||
|
||||
constructor(private _metadata: ComponentMetadata) {
|
||||
constructor(readonly designer: Designer, private _metadata: ComponentMetadata) {
|
||||
this.parseMetadata(_metadata);
|
||||
}
|
||||
|
||||
@ -173,6 +214,7 @@ export class ComponentMeta {
|
||||
this._isContainer = component.isContainer ? true : false;
|
||||
this._isModal = component.isModal ? true : false;
|
||||
this._descriptor = component.descriptor;
|
||||
this._rectSelector = component.rectSelector;
|
||||
if (component.nestingRule) {
|
||||
const { parentWhitelist, childWhitelist } = component.nestingRule;
|
||||
this.parentWhitelist = ensureAList(parentWhitelist);
|
||||
@ -187,7 +229,7 @@ export class ComponentMeta {
|
||||
private transformMetadata(metadta: ComponentMetadata): TransformedComponentMetadata {
|
||||
const result = metadataTransducers.reduce((prevMetadata, current) => {
|
||||
return current(prevMetadata);
|
||||
}, metadta);
|
||||
}, preprocessMetadata(metadta));
|
||||
|
||||
if (!result.configure) {
|
||||
result.configure = {};
|
||||
@ -199,6 +241,18 @@ export class ComponentMeta {
|
||||
return this.componentName === 'Page' || this.componentName === 'Block' || this.componentName === 'Component';
|
||||
}
|
||||
|
||||
@computed get availableActions() {
|
||||
let { disableBehaviors, actions } = this._transformedMetadata?.configure.component || {};
|
||||
actions = builtinComponentActions.concat(this.designer.getGlobalComponentActions() || [], actions || []);
|
||||
if (!disableBehaviors && this.isRootComponent()) {
|
||||
disableBehaviors = ['copy', 'remove'];
|
||||
}
|
||||
if (disableBehaviors) {
|
||||
return actions.filter(action => disableBehaviors!.indexOf(action.name) < 0);
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
set metadata(metadata: ComponentMetadata) {
|
||||
this._metadata = metadata;
|
||||
this.parseMetadata(metadata);
|
||||
@ -222,3 +276,87 @@ export class ComponentMeta {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function preprocessMetadata(metadata: ComponentMetadata): TransformedComponentMetadata {
|
||||
if (metadata.configure) {
|
||||
if (Array.isArray(metadata.configure)) {
|
||||
return {
|
||||
...metadata,
|
||||
configure: {
|
||||
props: metadata.configure,
|
||||
},
|
||||
};
|
||||
}
|
||||
return metadata as any;
|
||||
}
|
||||
|
||||
return {
|
||||
...metadata,
|
||||
configure: {},
|
||||
};
|
||||
}
|
||||
|
||||
registerMetadataTransducer(metadata => {
|
||||
const { configure, componentName } = metadata;
|
||||
const { component = {} } = configure;
|
||||
if (!component.nestingRule) {
|
||||
let m;
|
||||
// uri match xx.Group set subcontrolling: true, childWhiteList
|
||||
if ((m = /^(.+)\.Group$/.exec(componentName))) {
|
||||
// component.subControlling = true;
|
||||
if (!component.nestingRule) {
|
||||
component.nestingRule = {
|
||||
childWhitelist: [`${m[1]}`],
|
||||
};
|
||||
}
|
||||
}
|
||||
// uri match xx.Node set selfControlled: false, parentWhiteList
|
||||
else if ((m = /^(.+)\.Node$/.exec(componentName))) {
|
||||
// component.selfControlled = false;
|
||||
component.nestingRule = {
|
||||
parentWhitelist: [`${m[1]}`, componentName],
|
||||
};
|
||||
}
|
||||
// uri match .Item .Node .Option set parentWhiteList
|
||||
else if ((m = /^(.+)\.(Item|Node|Option)$/.exec(componentName))) {
|
||||
component.nestingRule = {
|
||||
parentWhitelist: [`${m[1]}`],
|
||||
};
|
||||
}
|
||||
}
|
||||
if (component.isModal == null && /Dialog/.test(componentName)) {
|
||||
component.isModal = true;
|
||||
}
|
||||
return {
|
||||
...metadata,
|
||||
configure: {
|
||||
...configure,
|
||||
component,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const builtinComponentActions: ComponentAction[] = [
|
||||
{
|
||||
name: 'remove',
|
||||
content: {
|
||||
icon: Remove,
|
||||
description: intl('remove'),
|
||||
action(node: Node) {
|
||||
node.remove();
|
||||
},
|
||||
},
|
||||
important: true,
|
||||
},
|
||||
{
|
||||
name: 'copy',
|
||||
content: {
|
||||
icon: Clone,
|
||||
description: intl('copy'),
|
||||
action(node: Node) {
|
||||
// node.remove();
|
||||
},
|
||||
},
|
||||
important: true,
|
||||
},
|
||||
];
|
||||
|
||||
@ -10,7 +10,7 @@ import Location, { LocationData, isLocationChildrenDetail } from './helper/locat
|
||||
import DocumentModel from './document/document-model';
|
||||
import Node, { insertChildren } from './document/node/node';
|
||||
import { isRootNode } from './document/node/root-node';
|
||||
import { ComponentMetadata, ComponentMeta } from './component-meta';
|
||||
import { ComponentMetadata, ComponentMeta, ComponentAction } from './component-meta';
|
||||
import Scroller, { IScrollable } from './helper/scroller';
|
||||
import { INodeSelector } from './simulator';
|
||||
import OffsetObserver, { createOffsetObserver } from './helper/offset-observer';
|
||||
@ -25,8 +25,9 @@ export interface DesignerProps {
|
||||
simulatorComponent?: ReactComponentType<any>;
|
||||
dragGhostComponent?: ReactComponentType<any>;
|
||||
suspensed?: boolean;
|
||||
componentsDescription?: ComponentMetadata[];
|
||||
componentMetadatas?: ComponentMetadata[];
|
||||
eventPipe?: EventEmitter;
|
||||
globalComponentActions?: ComponentAction[];
|
||||
onMount?: (designer: Designer) => void;
|
||||
onDragstart?: (e: LocateEvent) => void;
|
||||
onDrag?: (e: LocateEvent) => void;
|
||||
@ -229,8 +230,8 @@ export default class Designer {
|
||||
if (props.suspensed !== this.props.suspensed && props.suspensed != null) {
|
||||
this.suspensed = props.suspensed;
|
||||
}
|
||||
if (props.componentsDescription !== this.props.componentsDescription && props.componentsDescription != null) {
|
||||
this.buildComponentMetasMap(props.componentsDescription);
|
||||
if (props.componentMetadatas !== this.props.componentMetadatas && props.componentMetadatas != null) {
|
||||
this.buildComponentMetasMap(props.componentMetadatas);
|
||||
}
|
||||
} else {
|
||||
// init hotkeys
|
||||
@ -246,8 +247,8 @@ export default class Designer {
|
||||
if (props.suspensed != null) {
|
||||
this.suspensed = props.suspensed;
|
||||
}
|
||||
if (props.componentsDescription != null) {
|
||||
this.buildComponentMetasMap(props.componentsDescription);
|
||||
if (props.componentMetadatas != null) {
|
||||
this.buildComponentMetasMap(props.componentMetadatas);
|
||||
}
|
||||
}
|
||||
this.props = props;
|
||||
@ -307,7 +308,7 @@ export default class Designer {
|
||||
meta.metadata = data;
|
||||
this._lostComponentMetasMap.delete(key);
|
||||
} else {
|
||||
meta = new ComponentMeta(data);
|
||||
meta = new ComponentMeta(this, data);
|
||||
}
|
||||
|
||||
this._componentMetasMap.set(key, meta);
|
||||
@ -315,6 +316,10 @@ export default class Designer {
|
||||
});
|
||||
}
|
||||
|
||||
getGlobalComponentActions(): ComponentAction[] | null {
|
||||
return this.props?.globalComponentActions || null;
|
||||
}
|
||||
|
||||
getComponentMeta(componentName: string, generateMetadata?: () => ComponentMetadata | null): ComponentMeta {
|
||||
if (this._componentMetasMap.has(componentName)) {
|
||||
return this._componentMetasMap.get(componentName)!;
|
||||
@ -324,7 +329,7 @@ export default class Designer {
|
||||
return this._lostComponentMetasMap.get(componentName)!;
|
||||
}
|
||||
|
||||
const meta = new ComponentMeta({
|
||||
const meta = new ComponentMeta(this, {
|
||||
componentName,
|
||||
...(generateMetadata ? generateMetadata() : null),
|
||||
});
|
||||
|
||||
@ -2,6 +2,7 @@ import { obx, computed } from '@recore/obx';
|
||||
import { INodeSelector, IViewport } from '../simulator';
|
||||
import { uniqueId } from '../../../../utils/unique-id';
|
||||
import { isRootNode } from '../document/node/root-node';
|
||||
import Node from '../document/node/node';
|
||||
|
||||
export default class OffsetObserver {
|
||||
readonly id = uniqueId('oobx');
|
||||
@ -10,39 +11,65 @@ export default class OffsetObserver {
|
||||
private lastOffsetTop?: number;
|
||||
private lastOffsetHeight?: number;
|
||||
private lastOffsetWidth?: number;
|
||||
@obx private height = 0;
|
||||
@obx private width = 0;
|
||||
@obx private left = 0;
|
||||
@obx private top = 0;
|
||||
@obx private _height = 0;
|
||||
@obx private _width = 0;
|
||||
@obx private _left = 0;
|
||||
@obx private _top = 0;
|
||||
@obx private _right = 0;
|
||||
@obx private _bottom = 0;
|
||||
|
||||
@computed get height() {
|
||||
return this.isRoot ? this.viewport.height : this._height * this.scale;
|
||||
}
|
||||
|
||||
@computed get width() {
|
||||
return this.isRoot ? this.viewport.width : this._width * this.scale;
|
||||
}
|
||||
|
||||
@computed get top() {
|
||||
return this.isRoot ? 0 : this._top * this.scale;
|
||||
}
|
||||
|
||||
@computed get left() {
|
||||
return this.isRoot ? 0 : this._left * this.scale;
|
||||
}
|
||||
|
||||
@computed get bottom() {
|
||||
return this.isRoot ? this.viewport.height : this._bottom * this.scale;
|
||||
}
|
||||
|
||||
@computed get right() {
|
||||
return this.isRoot ? this.viewport.width : this._right * this.scale;
|
||||
}
|
||||
|
||||
@obx hasOffset = false;
|
||||
@computed get offsetLeft() {
|
||||
if (this.isRoot) {
|
||||
return this.viewport.scrollX;
|
||||
return this.viewport.scrollX * this.scale;
|
||||
}
|
||||
if (!this.viewport.scrolling || this.lastOffsetLeft == null) {
|
||||
this.lastOffsetLeft = (this.left + this.viewport.scrollX) * this.scale;
|
||||
this.lastOffsetLeft = this.left + this.viewport.scrollX * this.scale;
|
||||
}
|
||||
return this.lastOffsetLeft;
|
||||
}
|
||||
@computed get offsetTop() {
|
||||
if (this.isRoot) {
|
||||
return this.viewport.scrollY;
|
||||
return this.viewport.scrollY * this.scale;
|
||||
}
|
||||
if (!this.viewport.scrolling || this.lastOffsetTop == null) {
|
||||
this.lastOffsetTop = (this.top + this.viewport.scrollY) * this.scale;
|
||||
this.lastOffsetTop = this.top + this.viewport.scrollY * this.scale;
|
||||
}
|
||||
return this.lastOffsetTop;
|
||||
}
|
||||
@computed get offsetHeight() {
|
||||
if (!this.viewport.scrolling || this.lastOffsetHeight == null) {
|
||||
this.lastOffsetHeight = this.isRoot ? this.viewport.height : this.height * this.scale;
|
||||
this.lastOffsetHeight = this.isRoot ? this.viewport.height : this.height;
|
||||
}
|
||||
return this.lastOffsetHeight;
|
||||
}
|
||||
@computed get offsetWidth() {
|
||||
if (!this.viewport.scrolling || this.lastOffsetWidth == null) {
|
||||
this.lastOffsetWidth = this.isRoot ? this.viewport.width : this.width * this.scale;
|
||||
this.lastOffsetWidth = this.isRoot ? this.viewport.width : this.width;
|
||||
}
|
||||
return this.lastOffsetWidth;
|
||||
}
|
||||
@ -52,11 +79,13 @@ export default class OffsetObserver {
|
||||
}
|
||||
|
||||
private pid: number | undefined;
|
||||
private viewport: IViewport;
|
||||
readonly viewport: IViewport;
|
||||
private isRoot: boolean;
|
||||
readonly node: Node;
|
||||
|
||||
constructor(readonly nodeInstance: INodeSelector) {
|
||||
const { node, instance } = nodeInstance;
|
||||
this.node = node;
|
||||
const doc = node.document;
|
||||
const host = doc.simulator!;
|
||||
this.isRoot = isRootNode(node);
|
||||
@ -75,16 +104,18 @@ export default class OffsetObserver {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = host.computeComponentInstanceRect(instance!);
|
||||
const rect = host.computeComponentInstanceRect(instance!, node.componentMeta.rectSelector);
|
||||
|
||||
if (!rect) {
|
||||
this.hasOffset = false;
|
||||
} else {
|
||||
if (!this.viewport.scrolling || !this.hasOffset) {
|
||||
this.height = rect.height;
|
||||
this.width = rect.width;
|
||||
this.left = rect.left;
|
||||
this.top = rect.top;
|
||||
this._height = rect.height;
|
||||
this._width = rect.width;
|
||||
this._left = rect.left;
|
||||
this._top = rect.top;
|
||||
this._right = rect.right;
|
||||
this._bottom = rect.bottom;
|
||||
this.hasOffset = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +134,7 @@ export interface ISimulator<P = object> extends ISensor {
|
||||
|
||||
computeRect(node: Node): DOMRect | null;
|
||||
|
||||
computeComponentInstanceRect(instance: ComponentInstance): DOMRect | null;
|
||||
computeComponentInstanceRect(instance: ComponentInstance, selector?: string): DOMRect | null;
|
||||
|
||||
findDOMNodes(instance: ComponentInstance): Array<Element | Text> | null;
|
||||
|
||||
|
||||
4
packages/designer/src/locale/en-US.json
Normal file
4
packages/designer/src/locale/en-US.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"copy": "Copy",
|
||||
"remove": "Remove"
|
||||
}
|
||||
10
packages/designer/src/locale/index.ts
Normal file
10
packages/designer/src/locale/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { createIntl } from '../../../globals';
|
||||
import en_US from './en-US.json';
|
||||
import zh_CN from './zh-CN.json';
|
||||
|
||||
const { intl, getLocale, setLocale } = createIntl({
|
||||
'en-US': en_US,
|
||||
'zh-CN': zh_CN,
|
||||
});
|
||||
|
||||
export { intl, getLocale, setLocale };
|
||||
4
packages/designer/src/locale/zh-CN.json
Normal file
4
packages/designer/src/locale/zh-CN.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"copy": "复制",
|
||||
"remove": "删除"
|
||||
}
|
||||
@ -1196,7 +1196,8 @@ export default {
|
||||
isContainer: true,
|
||||
nestingRule: {
|
||||
childWhitelist: 'Select.Option'
|
||||
}
|
||||
},
|
||||
rectSelector: '.next-select',
|
||||
},
|
||||
props: [
|
||||
{
|
||||
@ -1762,6 +1763,26 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Dialog: {
|
||||
componentName: 'Dialog',
|
||||
title: '弹窗',
|
||||
devMode: 'proCode',
|
||||
npm: {
|
||||
package: '@alifd/next',
|
||||
version: '1.19.18',
|
||||
destructuring: true,
|
||||
exportName: 'Dialog'
|
||||
},
|
||||
props: [{
|
||||
name: 'title',
|
||||
propType: 'string'
|
||||
}],
|
||||
configure: {
|
||||
component: {
|
||||
rectSelector: '.next-dialog'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
componentList: [
|
||||
@ -1889,6 +1910,30 @@ export default {
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
componentName: 'Dialog',
|
||||
libraryId: 3,
|
||||
title: '弹窗',
|
||||
icon: '',
|
||||
snippets: [
|
||||
{
|
||||
title: '弹窗',
|
||||
screenshot: '',
|
||||
schema: {
|
||||
componentName: 'Dialog',
|
||||
props: {
|
||||
title: '这是一个dialog',
|
||||
visible: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
compoentName: 'Div'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -168,7 +168,7 @@ export default class DesignerPlugin extends PureComponent<PluginProps> {
|
||||
className="lowcode-plugin-designer"
|
||||
defaultSchema={SCHEMA as any}
|
||||
eventPipe={editor as any}
|
||||
componentsDescription={Object.values(assets.components) as any}
|
||||
componentMetadatas={Object.values(assets.components) as any}
|
||||
simulatorProps={{
|
||||
library: Object.values(assets.packages),
|
||||
}}
|
||||
|
||||
6
packages/globals/.eslintignore
Normal file
6
packages/globals/.eslintignore
Normal file
@ -0,0 +1,6 @@
|
||||
.idea/
|
||||
.vscode/
|
||||
build/
|
||||
.*
|
||||
~*
|
||||
node_modules
|
||||
3
packages/globals/.eslintrc
Normal file
3
packages/globals/.eslintrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./node_modules/@recore/config/.eslintrc"
|
||||
}
|
||||
6
packages/globals/.prettierrc
Normal file
6
packages/globals/.prettierrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
1
packages/globals/README.md
Normal file
1
packages/globals/README.md
Normal file
@ -0,0 +1 @@
|
||||
shared globals
|
||||
43
packages/globals/package.json
Normal file
43
packages/globals/package.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "@ali/lowcode-globals",
|
||||
"version": "0.0.0",
|
||||
"description": "xxx for Ali lowCode engine",
|
||||
"main": "src/index.ts",
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "ava",
|
||||
"test:snapshot": "ava --update-snapshots"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alifd/next": "^1.19.16",
|
||||
"classnames": "^2.2.6",
|
||||
"react": "^16",
|
||||
"react-dom": "^16.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@recore/config": "^2.0.0",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/node": "^13.7.1",
|
||||
"@types/react": "^16",
|
||||
"@types/react-dom": "^16",
|
||||
"eslint": "^6.5.1",
|
||||
"prettier": "^1.18.2",
|
||||
"tslib": "^1.9.3",
|
||||
"typescript": "^3.1.3",
|
||||
"ts-node": "^8.0.1"
|
||||
},
|
||||
"ava": {
|
||||
"compileEnhancements": false,
|
||||
"snapshotDir": "test/fixtures/__snapshots__",
|
||||
"extensions": [
|
||||
"ts"
|
||||
],
|
||||
"require": [
|
||||
"ts-node/register"
|
||||
]
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
2
packages/globals/src/components/index.ts
Normal file
2
packages/globals/src/components/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './tip';
|
||||
export * from './title';
|
||||
@ -1,4 +1,4 @@
|
||||
import { uniqueId } from '../../../utils/unique-id';
|
||||
import { uniqueId } from '../../../../utils/unique-id';
|
||||
import { Component, ReactNode } from 'react';
|
||||
import { saveTips } from './tip-handler';
|
||||
|
||||
@ -3,12 +3,7 @@ import { Icon } from '@alifd/next';
|
||||
import classNames from 'classnames';
|
||||
import EmbedTip, { TipConfig } from '../tip/embed-tip';
|
||||
import './title.less';
|
||||
|
||||
export interface IconConfig {
|
||||
type: string;
|
||||
size?: number | 'small' | 'xxs' | 'xs' | 'medium' | 'large' | 'xl' | 'xxl' | 'xxxl' | 'inherit';
|
||||
className?: string;
|
||||
}
|
||||
import { IconConfig, createIcon } from '../../utils';
|
||||
|
||||
export interface TitleConfig {
|
||||
label?: ReactNode;
|
||||
@ -19,7 +14,7 @@ export interface TitleConfig {
|
||||
|
||||
export type TitleContent = string | ReactElement | TitleConfig;
|
||||
|
||||
export default class Title extends Component<{ title: TitleContent; onClick?: () => void }> {
|
||||
export class Title extends Component<{ title: TitleContent; onClick?: () => void }> {
|
||||
render() {
|
||||
let { title } = this.props;
|
||||
if (isValidElement(title)) {
|
||||
@ -29,15 +24,7 @@ export default class Title extends Component<{ title: TitleContent; onClick?: ()
|
||||
title = { label: title }; // tslint:disable-line
|
||||
}
|
||||
|
||||
let icon = null;
|
||||
if (title.icon) {
|
||||
if (isValidElement(title.icon)) {
|
||||
icon = title.icon;
|
||||
} else {
|
||||
const iconProps = typeof title.icon === 'string' ? { type: title.icon } : title.icon;
|
||||
icon = <Icon {...iconProps} />;
|
||||
}
|
||||
}
|
||||
const icon = title.icon ? createIcon(title.icon) : null;
|
||||
|
||||
let tip: any = null;
|
||||
if (title.tip) {
|
||||
11
packages/globals/src/icons/clone.tsx
Normal file
11
packages/globals/src/icons/clone.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { IconBase, IconBaseProps } from "./icon-base";
|
||||
|
||||
export function Clone(props: IconBaseProps) {
|
||||
return (
|
||||
<IconBase viewBox="0 0 1024 1024" {...props}>
|
||||
<path d="M192 256.16C192 220.736 220.704 192 256.16 192h639.68C931.264 192 960 220.704 960 256.16v639.68A64.16 64.16 0 0 1 895.84 960H256.16A64.16 64.16 0 0 1 192 895.84V256.16z m64 31.584v576.512a32 32 0 0 0 31.744 31.744h576.512a32 32 0 0 0 31.744-31.744V287.744A32 32 0 0 0 864.256 256H287.744A32 32 0 0 0 256 287.744zM288 192v64h64V192H288z m128 0v64h64V192h-64z m128 0v64h64V192h-64z m128 0v64h64V192h-64z m128 0v64h64V192h-64z m96 96v64h64V288h-64z m0 128v64h64v-64h-64z m0 128v64h64v-64h-64z m0 128v64h64v-64h-64z m0 128v64h64v-64h-64z m-96 96v64h64v-64h-64z m-128 0v64h64v-64h-64z m-128 0v64h64v-64h-64z m-128 0v64h64v-64h-64z m-128 0v64h64v-64H288z m-96-96v64h64v-64H192z m0-128v64h64v-64H192z m0-128v64h64v-64H192z m0-128v64h64v-64H192z m0-128v64h64V288H192z m160 416c0-17.664 14.592-32 32.064-32h319.872a31.968 31.968 0 1 1 0 64h-319.872A31.968 31.968 0 0 1 352 704z m0-128c0-17.664 14.4-32 32.224-32h383.552c17.792 0 32.224 14.208 32.224 32 0 17.664-14.4 32-32.224 32H384.224A32.032 32.032 0 0 1 352 576z m0-128c0-17.664 14.4-32 32.224-32h383.552c17.792 0 32.224 14.208 32.224 32 0 17.664-14.4 32-32.224 32H384.224A32.032 32.032 0 0 1 352 448z m512 47.936V192h-64V159.968A31.776 31.776 0 0 0 768.032 128H160A31.776 31.776 0 0 0 128 159.968V768c0 17.92 14.304 31.968 31.968 31.968H192v64h303.936H128.128A63.968 63.968 0 0 1 64 799.872V128.128C64 92.704 92.48 64 128.128 64h671.744C835.296 64 864 92.48 864 128.128v367.808z"/>
|
||||
</IconBase>
|
||||
);
|
||||
}
|
||||
|
||||
Clone.displayName = 'Clone';
|
||||
10
packages/globals/src/icons/hidden.tsx
Normal file
10
packages/globals/src/icons/hidden.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { IconBase, IconBaseProps } from "./icon-base";
|
||||
|
||||
export function Hidden(props: IconBaseProps) {
|
||||
return (
|
||||
<IconBase viewBox="0 0 1024 1024" {...props}>
|
||||
<path d="M183.423543 657.078213l163.499771-98.484012c-4.233418-14.908548-6.646374-30.585599-6.646374-46.852074 0-94.665033 76.739778-171.404812 171.404812-171.404812 45.983287 0 87.641059 18.20871 118.42518 47.679929l129.791042-78.17957c-73.254398-41.73145-157.866471-65.812915-248.216221-65.812915-192.742792 0-360.068705 108.505249-444.453604 267.715321C96.636944 567.228859 136.301316 616.355743 183.423543 657.078213zM841.253886 367.552144l-164.382884 99.015108c3.934612 14.415314 6.215562 29.513174 6.215562 45.174875 0 94.665033-76.739778 171.404812-171.404812 171.404812-45.361117 0-86.484723-17.747199-117.142977-46.515407l-129.419582 77.955466c72.874751 41.149189 156.893306 64.871473 246.563582 64.871473 192.742792 0 360.068705-108.505249 444.453604-267.717368C927.000805 456.773188 887.794875 408.054603 841.253886 367.552144zM420.280042 511.741104c0 0.550539 0.152473 1.060145 0.161682 1.608637l135.080511-81.366146c-13.065574-7.198959-27.854395-11.658528-43.826158-11.658528C461.20922 420.325068 420.280042 461.254246 420.280042 511.741104zM447.739441 576.947198l69.02098-41.574884L948.364369 275.395234c10.812253-6.512321 14.297634-20.558222 7.785314-31.369452-6.512321-10.812253-20.556175-14.296611-31.368428-7.785314L575.654762 446.537056l0 0-151.20577 91.078345 0 0L75.027787 748.090043c-10.812253 6.512321-14.297634 20.556175-7.785314 31.368428 6.512321 10.812253 20.556175 14.297634 31.369452 7.785314L447.739441 576.947198 447.739441 576.947198zM511.696078 603.157139c50.487881 0 91.416036-40.928155 91.416036-91.416036 0-0.549515-0.152473-1.057075-0.161682-1.605567l-135.079488 81.364099C480.935494 598.699618 495.724315 603.157139 511.696078 603.157139z" />
|
||||
</IconBase>
|
||||
);
|
||||
}
|
||||
Hidden.displayName = 'Hidden';
|
||||
40
packages/globals/src/icons/icon-base.tsx
Normal file
40
packages/globals/src/icons/icon-base.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
const SizePresets: any = {
|
||||
xsmall: 8,
|
||||
small: 12,
|
||||
medium: 16,
|
||||
large: 20,
|
||||
xlarge: 30,
|
||||
};
|
||||
|
||||
export interface IconBaseProps {
|
||||
className?: string;
|
||||
fill?: string;
|
||||
size?: 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | number;
|
||||
viewBox: string;
|
||||
children?: ReactNode;
|
||||
style?: object;
|
||||
};
|
||||
|
||||
export function IconBase({ fill, size = 'medium', viewBox, style, children, ...props }: IconBaseProps) {
|
||||
if (SizePresets.hasOwnProperty(size)) {
|
||||
size = SizePresets[size];
|
||||
}
|
||||
|
||||
return (
|
||||
<svg
|
||||
fill="currentColor"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox={viewBox}
|
||||
{...props}
|
||||
style={{
|
||||
verticalAlign: 'middle',
|
||||
color: fill,
|
||||
...style,
|
||||
}}
|
||||
>{children}</svg>
|
||||
);
|
||||
}
|
||||
5
packages/globals/src/icons/index.ts
Normal file
5
packages/globals/src/icons/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './clone';
|
||||
export * from './hidden';
|
||||
export * from './remove';
|
||||
export * from './settings';
|
||||
export * from './icon-base';
|
||||
10
packages/globals/src/icons/remove.tsx
Normal file
10
packages/globals/src/icons/remove.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { IconBase, IconBaseProps } from './icon-base';
|
||||
|
||||
export function Remove(props: IconBaseProps) {
|
||||
return (
|
||||
<IconBase viewBox="0 0 1024 1024" {...props}>
|
||||
<path d="M224 256v639.84A64 64 0 0 0 287.84 960h448.32A64 64 0 0 0 800 895.84V256h64a32 32 0 1 0 0-64H160a32 32 0 1 0 0 64h64zM384 96c0-17.664 14.496-32 31.904-32h192.192C625.696 64 640 78.208 640 96c0 17.664-14.496 32-31.904 32H415.904A31.872 31.872 0 0 1 384 96z m-96 191.744C288 270.208 302.4 256 320.224 256h383.552C721.6 256 736 270.56 736 287.744v576.512C736 881.792 721.6 896 703.776 896H320.224A32.224 32.224 0 0 1 288 864.256V287.744zM352 352c0-17.696 14.208-32.032 32-32.032 17.664 0 32 14.24 32 32v448c0 17.664-14.208 32-32 32-17.664 0-32-14.24-32-32V352z m128 0c0-17.696 14.208-32.032 32-32.032 17.664 0 32 14.24 32 32v448c0 17.664-14.208 32-32 32-17.664 0-32-14.24-32-32V352z m128 0c0-17.696 14.208-32.032 32-32.032 17.664 0 32 14.24 32 32v448c0 17.664-14.208 32-32 32-17.664 0-32-14.24-32-32V352z" />
|
||||
</IconBase>
|
||||
);
|
||||
}
|
||||
Remove.displayName = 'Remove';
|
||||
12
packages/globals/src/icons/settings.tsx
Normal file
12
packages/globals/src/icons/settings.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { IconBase, IconBaseProps } from './icon-base';
|
||||
|
||||
export function Setting(props: IconBaseProps) {
|
||||
return (
|
||||
<IconBase viewBox="0 0 1024 1024" {...props}>
|
||||
<path d="M965.824 405.952a180.48 180.48 0 0 1-117.12-85.376 174.464 174.464 0 0 1-16-142.08 22.208 22.208 0 0 0-7.04-23.552 480.576 480.576 0 0 0-153.6-89.216 23.104 23.104 0 0 0-24.32 5.76 182.208 182.208 0 0 1-135.68 57.92 182.208 182.208 0 0 1-133.12-56.64 23.104 23.104 0 0 0-26.88-7.04 478.656 478.656 0 0 0-153.6 89.856 22.208 22.208 0 0 0-7.04 23.552 174.464 174.464 0 0 1-16 141.44A180.48 180.48 0 0 1 58.24 405.952a22.4 22.4 0 0 0-17.28 17.792 455.08 455.08 0 0 0 0 176.512 22.4 22.4 0 0 0 17.28 17.792 180.48 180.48 0 0 1 117.12 84.736c25.408 42.944 31.232 94.592 16 142.08a22.208 22.208 0 0 0 7.04 23.552A480.576 480.576 0 0 0 352 957.632h7.68a23.04 23.04 0 0 0 16.64-7.04 184.128 184.128 0 0 1 266.944 0c6.592 8.96 18.752 11.968 28.8 7.04a479.36 479.36 0 0 0 156.16-88.576 22.208 22.208 0 0 0 7.04-23.552 174.464 174.464 0 0 1 13.44-142.72 180.48 180.48 0 0 1 117.12-84.736 22.4 22.4 0 0 0 17.28-17.792 452.613 452.613 0 0 0 0-176.512 23.04 23.04 0 0 0-17.28-17.792z m-42.88 169.408a218.752 218.752 0 0 0-128 98.112 211.904 211.904 0 0 0-21.76 156.736 415.936 415.936 0 0 1-112 63.68 217.472 217.472 0 0 0-149.12-63.68 221.312 221.312 0 0 0-149.12 63.68 414.592 414.592 0 0 1-112-63.68c12.8-53.12 4.288-109.12-23.68-156.096A218.752 218.752 0 0 0 101.12 575.36a386.176 386.176 0 0 1 0-127.36 218.752 218.752 0 0 0 128-98.112c27.2-47.552 34.944-103.68 21.76-156.8a415.296 415.296 0 0 1 112-63.68A221.44 221.44 0 0 0 512 187.392a218.24 218.24 0 0 0 149.12-57.984 413.952 413.952 0 0 1 112 63.744 211.904 211.904 0 0 0 23.04 156.096 218.752 218.752 0 0 0 128 98.112 386.65 386.65 0 0 1 0 127.36l-1.28 0.64z" />
|
||||
<path d="M512 320.576c-105.984 0-192 85.568-192 191.104a191.552 191.552 0 0 0 192 191.104c106.112 0 192.064-85.568 192.064-191.104a190.72 190.72 0 0 0-56.256-135.168 192.448 192.448 0 0 0-135.744-55.936z m0 318.528c-70.656 0-128-57.088-128-127.424 0-70.4 57.344-127.36 128-127.36 70.72 0 128 56.96 128 127.36 0 33.792-13.44 66.176-37.44 90.112a128.32 128.32 0 0 1-90.496 37.312z" />
|
||||
</IconBase>
|
||||
);
|
||||
}
|
||||
|
||||
Setting.displayName = 'Setting';
|
||||
4
packages/globals/src/index.ts
Normal file
4
packages/globals/src/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './intl';
|
||||
export * from './components';
|
||||
export * from './utils';
|
||||
export * from './icons';
|
||||
124
packages/globals/src/intl/ali-global-locale.ts
Normal file
124
packages/globals/src/intl/ali-global-locale.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { EventEmitter } from 'events';
|
||||
const languageMap: { [key: string]: string } = {
|
||||
en: 'en-US',
|
||||
zh: 'zh-CN',
|
||||
zt: 'zh-TW',
|
||||
es: 'es-ES',
|
||||
pt: 'pt-PT',
|
||||
fr: 'fr-FR',
|
||||
de: 'de-DE',
|
||||
it: 'it-IT',
|
||||
ru: 'ru-RU',
|
||||
ja: 'ja-JP',
|
||||
ko: 'ko-KR',
|
||||
ar: 'ar-SA',
|
||||
tr: 'tr-TR',
|
||||
th: 'th-TH',
|
||||
vi: 'vi-VN',
|
||||
nl: 'nl-NL',
|
||||
he: 'iw-IL',
|
||||
id: 'in-ID',
|
||||
pl: 'pl-PL',
|
||||
hi: 'hi-IN',
|
||||
uk: 'uk-UA',
|
||||
ms: 'ms-MY',
|
||||
tl: 'tl-PH',
|
||||
};
|
||||
|
||||
const LowcodeConfigKey = 'ali-lowcode-config';
|
||||
|
||||
class AliGlobalLocale {
|
||||
private locale: string = '';
|
||||
private emitter = new EventEmitter();
|
||||
|
||||
constructor() {
|
||||
this.emitter.setMaxListeners(0);
|
||||
}
|
||||
|
||||
setLocale(locale: string) {
|
||||
this.locale = locale;
|
||||
if (hasLocalStorage(window)) {
|
||||
const store = window.localStorage;
|
||||
let config: any;
|
||||
try {
|
||||
config = JSON.parse(store.getItem(LowcodeConfigKey) || '');
|
||||
} catch (e) {
|
||||
// ignore;
|
||||
}
|
||||
|
||||
if (config && typeof config === 'object') {
|
||||
config.locale = locale;
|
||||
} else {
|
||||
config = { locale };
|
||||
}
|
||||
|
||||
store.setItem(LowcodeConfigKey, JSON.stringify(config));
|
||||
}
|
||||
}
|
||||
|
||||
getLocale() {
|
||||
if (this.locale) {
|
||||
return this.locale;
|
||||
}
|
||||
|
||||
const { g_config, navigator } = window as any;
|
||||
if (hasLocalStorage(window)) {
|
||||
const store = window.localStorage;
|
||||
let config: any;
|
||||
try {
|
||||
config = JSON.parse(store.getItem(LowcodeConfigKey) || '');
|
||||
} catch (e) {
|
||||
// ignore;
|
||||
}
|
||||
if (config?.locale) {
|
||||
this.locale = (config.locale || '').replace('_', '-');
|
||||
return this.locale;
|
||||
}
|
||||
} else if (g_config) {
|
||||
if (g_config.locale) {
|
||||
this.locale = languageMap[g_config.locale] || (g_config.locale || '').replace('_', '-');
|
||||
return this.locale;
|
||||
}
|
||||
}
|
||||
|
||||
if (navigator.language) {
|
||||
this.locale = (navigator.language as string).replace('_', '-');
|
||||
}
|
||||
|
||||
// IE10 及更低版本使用 browserLanguage
|
||||
if (navigator.browserLanguage) {
|
||||
const it = navigator.browserLanguage.split('-');
|
||||
this.locale = it[0];
|
||||
if (it[1]) {
|
||||
this.locale += '-' + it[1].toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.locale) {
|
||||
this.locale = 'zh-CN';
|
||||
}
|
||||
|
||||
return this.locale;
|
||||
}
|
||||
|
||||
onLocaleChange(fn: (locale: string) => void): () => void {
|
||||
this.emitter.on('localechange', fn);
|
||||
return () => {
|
||||
this.emitter.removeListener('localechange', fn);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function hasLocalStorage(obj: any): obj is WindowLocalStorage {
|
||||
return obj.localStorage;
|
||||
}
|
||||
|
||||
let globalLocale: AliGlobalLocale;
|
||||
if ((window as any).__aliGlobalLocale) {
|
||||
globalLocale = (window as any).__aliGlobalLocale as any;
|
||||
} else {
|
||||
globalLocale = new AliGlobalLocale();
|
||||
(window as any).__aliGlobalLocale = globalLocale;
|
||||
}
|
||||
|
||||
export { globalLocale };
|
||||
122
packages/globals/src/intl/index.tsx
Normal file
122
packages/globals/src/intl/index.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import { globalLocale } from './ali-global-locale';
|
||||
import { PureComponent, ReactNode } from 'react';
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
export interface I18nData {
|
||||
type: 'i18n';
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export function isI18nData(obj: any): obj is I18nData {
|
||||
return obj && obj.type === 'i18n';
|
||||
}
|
||||
|
||||
export function localeFormat(data: any, params?: object): string {
|
||||
if (!isI18nData(data)) {
|
||||
return data;
|
||||
}
|
||||
const locale = globalLocale.getLocale();
|
||||
const tpl = data[locale];
|
||||
if (tpl == null) {
|
||||
return `##intl null@${locale}##`;
|
||||
}
|
||||
return injectVars(tpl, params);
|
||||
}
|
||||
|
||||
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)) {
|
||||
return <Intl data={data} params={params} />;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
export function createIntl(
|
||||
instance: string | object,
|
||||
): {
|
||||
intl(id: string, params?: object): ReactNode;
|
||||
getIntlString(id: string, params?: object): string;
|
||||
getLocale(): string;
|
||||
setLocale(locale: string): void;
|
||||
} {
|
||||
let lastLocale: string | undefined;
|
||||
let data: any = {};
|
||||
function useLocale(locale: string) {
|
||||
lastLocale = locale;
|
||||
if (typeof instance === 'string') {
|
||||
if ((window as any)[instance]) {
|
||||
data = (window as any)[instance][locale] || {};
|
||||
} else {
|
||||
const key = `${instance}_${locale.toLocaleLowerCase()}`;
|
||||
data = (window as any)[key] || {};
|
||||
}
|
||||
} else if (instance && typeof instance === 'object') {
|
||||
data = (instance as any)[locale] || {};
|
||||
}
|
||||
}
|
||||
|
||||
useLocale(globalLocale.getLocale());
|
||||
|
||||
function getIntlString(key: string, params?: object): string {
|
||||
const str = data[key];
|
||||
|
||||
if (str == null) {
|
||||
return `##intl null@${key}##`;
|
||||
}
|
||||
|
||||
return injectVars(str, params);
|
||||
}
|
||||
|
||||
class Intl extends PureComponent<{ id: string; params?: object }> {
|
||||
private dispose = globalLocale.onLocaleChange(locale => {
|
||||
if (lastLocale !== locale) {
|
||||
useLocale(locale);
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
componentWillUnmount() {
|
||||
this.dispose();
|
||||
}
|
||||
render() {
|
||||
const { id, params } = this.props;
|
||||
return getIntlString(id, params);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
intl(id: string, params?: object) {
|
||||
return <Intl id={id} params={params} />;
|
||||
},
|
||||
getIntlString,
|
||||
getLocale() {
|
||||
return globalLocale.getLocale();
|
||||
},
|
||||
setLocale(locale: string) {
|
||||
globalLocale.setLocale(locale);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export { globalLocale };
|
||||
34
packages/globals/src/utils/create-icon.tsx
Normal file
34
packages/globals/src/utils/create-icon.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { Icon } from '@alifd/next';
|
||||
import { isValidElement, ReactNode, ComponentType, createElement, cloneElement, ReactElement } from 'react';
|
||||
import { isReactComponent } from './is-react';
|
||||
|
||||
export interface IconConfig {
|
||||
type: string;
|
||||
size?: number | 'small' | 'xxs' | 'xs' | 'medium' | 'large' | 'xl' | 'xxl' | 'xxxl' | 'inherit';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export type IconType = string | ReactElement | ComponentType<any> | IconConfig;
|
||||
|
||||
const URL_RE = /^(https?:)\/\//i;
|
||||
|
||||
export function createIcon(icon: IconType, props?: object): ReactNode {
|
||||
if (typeof icon === 'string') {
|
||||
if (URL_RE.test(icon)) {
|
||||
return <img src={icon} {...props} />;
|
||||
}
|
||||
return <Icon type={icon} {...props} />;
|
||||
}
|
||||
if (isValidElement(icon)) {
|
||||
return cloneElement(icon, {...props});
|
||||
}
|
||||
if (isReactComponent(icon)) {
|
||||
return createElement(icon, {...props});
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
return <Icon {...icon} {...props} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
2
packages/globals/src/utils/index.ts
Normal file
2
packages/globals/src/utils/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './create-icon';
|
||||
export * from './is-react';
|
||||
9
packages/globals/src/utils/is-react.ts
Normal file
9
packages/globals/src/utils/is-react.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { ComponentClass, Component, ComponentType } from 'react';
|
||||
|
||||
export function isReactClass(obj: any): obj is ComponentClass<any> {
|
||||
return obj && obj.prototype && (obj.prototype.isReactComponent || obj.prototype instanceof Component);
|
||||
}
|
||||
|
||||
export function isReactComponent(obj: any): obj is ComponentType<any> {
|
||||
return obj && (isReactClass(obj) || typeof obj === 'function');
|
||||
}
|
||||
9
packages/globals/tsconfig.json
Normal file
9
packages/globals/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./node_modules/@recore/config/tsconfig",
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": [
|
||||
"./src/"
|
||||
]
|
||||
}
|
||||
@ -5,7 +5,7 @@ import { SettingField, SetterType, FieldConfig, SetterConfig } from '../../main'
|
||||
import './style.less';
|
||||
import { createSettingFieldView } from '../../settings-pane';
|
||||
import { PopupContext, PopupPipe } from '../../popup';
|
||||
import Title from '../../title';
|
||||
import { Title } from '../../../../globals';
|
||||
|
||||
interface ArraySetterState {
|
||||
items: SettingField[];
|
||||
|
||||
@ -3,7 +3,7 @@ import { Icon, Button } from '@alifd/next';
|
||||
import { FieldConfig, SettingField, SetterType } from '../../main';
|
||||
import { createSettingFieldView } from '../../settings-pane';
|
||||
import { PopupContext, PopupPipe } from '../../popup';
|
||||
import Title from '../../title';
|
||||
import { Title } from '../../../..//globals';
|
||||
import './style.less';
|
||||
|
||||
export default class ObjectSetter extends Component<{
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Component } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Icon } from '@alifd/next';
|
||||
import Title, { TitleContent } from '../title';
|
||||
import { Title, TitleContent } from '../../../globals';
|
||||
import './index.less';
|
||||
|
||||
export interface FieldProps {
|
||||
|
||||
@ -2,13 +2,12 @@ import React, { Component } from 'react';
|
||||
import { Tab, Breadcrumb, Icon } from '@alifd/next';
|
||||
import { SettingsMain, SettingField, isSettingField } from './main';
|
||||
import './style.less';
|
||||
import Title from './title';
|
||||
import { Title, TipContainer } from '../../globals';
|
||||
import SettingsPane, { registerSetter, createSetterContent, getSetter, createSettingFieldView } from './settings-pane';
|
||||
import Node from '../../designer/src/designer/document/node/node';
|
||||
import ArraySetter from './builtin-setters/array-setter';
|
||||
import ObjectSetter from './builtin-setters/object-setter';
|
||||
import './register-transducer';
|
||||
import { TipContainer } from './tip';
|
||||
|
||||
export default class SettingsMainView extends Component {
|
||||
private main: SettingsMain;
|
||||
|
||||
@ -2,7 +2,7 @@ import { EventEmitter } from 'events';
|
||||
import { uniqueId } from '../../utils/unique-id';
|
||||
import { ComponentMeta } from '../../designer/src/designer/component-meta';
|
||||
import Node from '../../designer/src/designer/document/node/node';
|
||||
import { TitleContent } from './title';
|
||||
import { TitleContent } from '../../globals';
|
||||
import { ReactElement, ComponentType as ReactComponentType, isValidElement } from 'react';
|
||||
import { isReactComponent } from '../../utils/is-react';
|
||||
import Designer from '../../designer/src/designer/designer';
|
||||
|
||||
@ -70,7 +70,7 @@ export function propTypeToSetter(propType: PropType): SetterType {
|
||||
};
|
||||
|
||||
case 'element':
|
||||
case 'node':
|
||||
case 'node': // TODO: use Mixin
|
||||
return {
|
||||
// slotSetter
|
||||
componentName: 'NodeSetter',
|
||||
@ -156,22 +156,9 @@ export function propTypeToSetter(propType: PropType): SetterType {
|
||||
|
||||
const EVENT_RE = /^on[A-Z][\w]*$/;
|
||||
|
||||
// parseProps
|
||||
registerMetadataTransducer(metadata => {
|
||||
if (metadata.configure) {
|
||||
if (Array.isArray(metadata.configure)) {
|
||||
return {
|
||||
...metadata,
|
||||
configure: {
|
||||
props: metadata.configure,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (metadata.configure.props) {
|
||||
return metadata as any;
|
||||
}
|
||||
}
|
||||
|
||||
const { configure = {} } = metadata;
|
||||
const { configure } = metadata;
|
||||
|
||||
if (!metadata.props) {
|
||||
return {
|
||||
@ -237,46 +224,7 @@ registerMetadataTransducer(metadata => {
|
||||
};
|
||||
});
|
||||
|
||||
registerMetadataTransducer(metadata => {
|
||||
const { configure = {}, componentName } = metadata;
|
||||
const { component = {} } = configure as any;
|
||||
if (!component.nestingRule) {
|
||||
let m;
|
||||
// uri match xx.Group set subcontrolling: true, childWhiteList
|
||||
if ((m = /^(.+)\.Group$/.exec(componentName))) {
|
||||
// component.subControlling = true;
|
||||
if (!component.nestingRule) {
|
||||
component.nestingRule = {
|
||||
childWhitelist: [`${m[1]}`],
|
||||
};
|
||||
}
|
||||
}
|
||||
// uri match xx.Node set selfControlled: false, parentWhiteList
|
||||
else if ((m = /^(.+)\.Node$/.exec(componentName))) {
|
||||
// component.selfControlled = false;
|
||||
component.nestingRule = {
|
||||
parentWhitelist: [`${m[1]}`, componentName],
|
||||
};
|
||||
}
|
||||
// uri match .Item .Node .Option set parentWhiteList
|
||||
else if ((m = /^(.+)\.(Item|Node|Option)$/.exec(componentName))) {
|
||||
component.nestingRule = {
|
||||
parentWhitelist: [`${m[1]}`],
|
||||
};
|
||||
}
|
||||
}
|
||||
if (component.isModal == null && /Dialog/.test(componentName)) {
|
||||
component.isModal = true;
|
||||
}
|
||||
return {
|
||||
...metadata,
|
||||
configure: {
|
||||
...configure,
|
||||
component,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// addon/platform custom
|
||||
registerMetadataTransducer(metadata => {
|
||||
const { componentName, configure = {} } = metadata;
|
||||
if (componentName === 'Leaf') {
|
||||
|
||||
@ -11,8 +11,7 @@ import {
|
||||
DynamicProps,
|
||||
} from './main';
|
||||
import { Field, FieldGroup } from './field';
|
||||
import { TitleContent } from './title';
|
||||
import { Balloon } from '@alifd/next';
|
||||
import { TitleContent } from '../../globals';
|
||||
import PopupService from './popup';
|
||||
|
||||
export type RegisteredSetter = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user