add toolbar&intl

This commit is contained in:
kangwei 2020-03-20 22:42:02 +08:00
parent 897d01ebff
commit f0a023df6d
52 changed files with 1293 additions and 417 deletions

View File

@ -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 } : {};
}
}

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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',
},
},
};

View File

@ -85,7 +85,7 @@ export class SimulatorRenderer {
}
@computed get designMode(): any {
return 'border';
return 'preview';
}
@obx.ref private _componentsMap = {};
@computed get componentsMap(): any {

View File

@ -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',
},
},
};

View File

@ -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,
},
];

View File

@ -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),
});

View File

@ -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;
}
}

View File

@ -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;

View File

@ -0,0 +1,4 @@
{
"copy": "Copy",
"remove": "Remove"
}

View 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 };

View File

@ -0,0 +1,4 @@
{
"copy": "复制",
"remove": "删除"
}

View File

@ -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'
}
]
}
}
]
}
]
}

View File

@ -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),
}}

View File

@ -0,0 +1,6 @@
.idea/
.vscode/
build/
.*
~*
node_modules

View File

@ -0,0 +1,3 @@
{
"extends": "./node_modules/@recore/config/.eslintrc"
}

View File

@ -0,0 +1,6 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 120,
"trailingComma": "all"
}

View File

@ -0,0 +1 @@
shared globals

View 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"
}

View File

@ -0,0 +1,2 @@
export * from './tip';
export * from './title';

View File

@ -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';

View File

@ -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) {

View 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';

View 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';

View 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>
);
}

View File

@ -0,0 +1,5 @@
export * from './clone';
export * from './hidden';
export * from './remove';
export * from './settings';
export * from './icon-base';

View 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';

View 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';

View File

@ -0,0 +1,4 @@
export * from './intl';
export * from './components';
export * from './utils';
export * from './icons';

View 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 };

View 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 };

View 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;
}

View File

@ -0,0 +1,2 @@
export * from './create-icon';
export * from './is-react';

View 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');
}

View File

@ -0,0 +1,9 @@
{
"extends": "./node_modules/@recore/config/tsconfig",
"compilerOptions": {
"experimentalDecorators": true
},
"include": [
"./src/"
]
}

View File

@ -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[];

View File

@ -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<{

View File

@ -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 {

View File

@ -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;

View File

@ -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';

View File

@ -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') {

View File

@ -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 = {