lowcode-engine/packages/designer/src/component-meta.ts
凤矗 2f9bb2567c fix: 🐛 title缺少icon字段,临时转接一下
需要后续相关同学增加api,已经标注
2020-05-05 14:38:19 +08:00

355 lines
9.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
ComponentMetadata,
NpmInfo,
NodeData,
NodeSchema,
ComponentAction,
TitleContent,
TransformedComponentMetadata,
NestingFilter,
isTitleConfig,
I18nData,
} from '@ali/lowcode-types';
import { computed } from '@ali/lowcode-editor-core';
import { Node, ParentalNode } from './document';
import { Designer } from './designer';
import { intlNode } from './locale';
import { IconContainer } from './icons/container';
import { IconPage } from './icons/page';
import { IconComponent } from './icons/component';
import { IconRemove } from './icons/remove';
import { IconClone } from './icons/clone';
import { ReactElement } from 'react';
function ensureAList(list?: string | string[]): string[] | null {
if (!list) {
return null;
}
if (!Array.isArray(list)) {
if (typeof list !== 'string') {
return null;
}
list = list.split(/ *[ ,|] */).filter(Boolean);
}
if (list.length < 1) {
return null;
}
return list;
}
function isRegExp(obj: any): obj is RegExp {
return obj && obj.test && obj.exec && obj.compile;
}
function buildFilter(rule?: string | string[] | RegExp | NestingFilter) {
if (!rule) {
return null;
}
if (typeof rule === 'function') {
return rule;
}
if (isRegExp(rule)) {
return (testNode: Node | NodeSchema) => rule.test(testNode.componentName);
}
const list = ensureAList(rule);
if (!list) {
return null;
}
return (testNode: Node | NodeSchema) => list.includes(testNode.componentName);
}
export class ComponentMeta {
readonly isComponentMeta = true;
private _npm?: NpmInfo;
get npm() {
return this._npm;
}
private _componentName?: string;
get componentName(): string {
return this._componentName!;
}
private _isContainer?: boolean;
get isContainer(): boolean {
return this._isContainer! || this.isRootComponent();
}
private _isModal?: boolean;
get isModal(): boolean {
return this._isModal!;
}
private _descriptor?: string;
get descriptor(): string | undefined {
return this._descriptor;
}
private _rectSelector?: string;
get rectSelector(): string | undefined {
return this._rectSelector;
}
private _transformedMetadata?: TransformedComponentMetadata;
get configure() {
const config = this._transformedMetadata?.configure;
return config?.combined || config?.props || [];
}
private parentWhitelist?: NestingFilter | null;
private childWhitelist?: NestingFilter | null;
private _title?: TitleContent;
get title(): string | I18nData | ReactElement {
// TODO: 标记下。这块需要康师傅加一下API页面正常渲染。
// string | i18nData | ReactElement
// TitleConfig title.label
if (isTitleConfig(this._title)) {
return (this._title.label as any) || this.componentName;
}
return this._title || this.componentName;
}
@computed get icon() {
// TODO: 标记下。这块需要康师傅加一下API页面正常渲染。
// give Slot default icon
// if _title is TitleConfig get _title.icon
return (
this._transformedMetadata?.icon ||
(this.componentName === 'Page' ? IconPage : this.isContainer ? IconContainer : IconComponent)
);
}
private _acceptable?: boolean;
get acceptable(): boolean {
return this._acceptable!;
}
constructor(readonly designer: Designer, metadata: ComponentMetadata) {
this.parseMetadata(metadata);
}
setNpm(info: NpmInfo) {
if (!this._npm) {
this._npm = info;
}
}
private parseMetadata(metadta: ComponentMetadata) {
const { componentName, npm } = metadta;
this._npm = npm;
this._componentName = componentName;
// 额外转换逻辑
this._transformedMetadata = this.transformMetadata(metadta);
const title = this._transformedMetadata.title;
if (title) {
this._title =
typeof title === 'string'
? {
type: 'i18n',
'en-US': this.componentName,
'zh-CN': title,
}
: title;
}
const { configure = {} } = this._transformedMetadata;
this._acceptable = false;
const { component } = configure;
if (component) {
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 = buildFilter(parentWhitelist);
this.childWhitelist = buildFilter(childWhitelist);
}
} else {
this._isContainer = false;
this._isModal = false;
}
}
private transformMetadata(metadta: ComponentMetadata): TransformedComponentMetadata {
const result = getRegisteredMetadataTransducers().reduce((prevMetadata, current) => {
return current(prevMetadata);
}, preprocessMetadata(metadta));
if (!result.configure) {
result.configure = {};
}
return result as any;
}
isRootComponent() {
return this.componentName === 'Page' || this.componentName === 'Block' || this.componentName === 'Component';
}
@computed get availableActions() {
let { disableBehaviors, actions } = this._transformedMetadata?.configure.component || {};
const disabled = ensureAList(disableBehaviors) || (this.isRootComponent() ? ['copy', 'remove'] : null);
actions = builtinComponentActions.concat(this.designer.getGlobalComponentActions() || [], actions || []);
if (disabled) {
if (disabled.includes('*')) {
return actions.filter((action) => action.condition === 'always');
}
return actions.filter((action) => disabled.indexOf(action.name) < 0);
}
return actions;
}
setMetadata(metadata: ComponentMetadata) {
this.parseMetadata(metadata);
}
getMetadata(): TransformedComponentMetadata {
return this._transformedMetadata!;
}
checkNestingUp(my: Node | NodeData, parent: ParentalNode) {
// 检查父子关系,直接约束型,在画布中拖拽直接掠过目标容器
if (this.parentWhitelist) {
return this.parentWhitelist(parent, my);
}
return true;
}
checkNestingDown(my: Node, target: Node | NodeSchema) {
// 检查父子关系,直接约束型,在画布中拖拽直接掠过目标容器
if (this.childWhitelist) {
return this.childWhitelist(target, my);
}
return true;
}
}
export function isComponentMeta(obj: any): obj is ComponentMeta {
return obj && obj.isComponentMeta;
}
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: {},
};
}
export interface MetadataTransducer {
(prev: TransformedComponentMetadata): TransformedComponentMetadata;
/**
* 0 - 9 system
* 10 - 99 builtin-plugin
* 100 - app & plugin
*/
level?: number;
/**
* use to replace TODO
*/
id?: string;
}
const metadataTransducers: MetadataTransducer[] = [];
export function registerMetadataTransducer(transducer: MetadataTransducer, level: number = 100, id?: string) {
transducer.level = level;
transducer.id = id;
const i = metadataTransducers.findIndex((item) => item.level != null && item.level > level);
if (i < 0) {
metadataTransducers.push(transducer);
} else {
metadataTransducers.splice(i, 0, transducer);
}
}
export function getRegisteredMetadataTransducers(): MetadataTransducer[] {
return metadataTransducers;
}
registerMetadataTransducer((metadata) => {
const { configure, componentName } = metadata;
const { component = {} } = configure;
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: IconRemove,
title: intlNode('remove'),
action(node: Node) {
node.remove();
},
},
important: true,
},
{
name: 'copy',
content: {
icon: IconClone,
title: intlNode('copy'),
action(node: Node) {
// node.remove();
},
},
important: true,
},
];
export function removeBuiltinComponentAction(name: string) {
const i = builtinComponentActions.findIndex((action) => action.name === name);
if (i > -1) {
builtinComponentActions.splice(i, 1);
}
}
export function addBuiltinComponentAction(action: ComponentAction) {
builtinComponentActions.push(action);
}