2024-01-25 16:38:58 +08:00

371 lines
9.9 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.

/* eslint-disable no-console */
/* eslint-disable no-new-func */
import logger from './logger';
import { IPublicTypeRootSchema, IPublicTypeNodeSchema, IPublicTypeJSSlot } from '@alilc/lowcode-types';
import { isI18nData, isJSExpression } from '@alilc/lowcode-utils';
import { isEmpty } from 'lodash';
import IntlMessageFormat from 'intl-messageformat';
import pkg from '../../package.json';
(window as any).sdkVersion = pkg.version;
export { pick, isEqualWith as deepEqual, cloneDeep as clone, isEmpty, throttle, debounce } from 'lodash';
const EXPRESSION_TYPE = {
JSEXPRESSION: 'JSExpression',
JSFUNCTION: 'JSFunction',
JSSLOT: 'JSSlot',
JSBLOCK: 'JSBlock',
I18N: 'i18n',
};
/**
* check if schema passed in is a valid schema
* @name isSchema
* @returns boolean
*/
export function isSchema(schema: any): schema is IPublicTypeNodeSchema {
if (isEmpty(schema)) {
return false;
}
// Leaf and Slot should be valid
if (schema.componentName === 'Leaf' || schema.componentName === 'Slot') {
return true;
}
if (Array.isArray(schema)) {
return schema.every((item) => isSchema(item));
}
// check if props is valid
const isValidProps = (props: any) => {
if (!props) {
return false;
}
if (isJSExpression(props)) {
return true;
}
return (typeof schema.props === 'object' && !Array.isArray(props));
};
return !!(schema.componentName && isValidProps(schema.props));
}
/**
* check if schema passed in is a container type, including : Component Block Page
* @param schema
* @returns boolean
*/
export function isFileSchema(schema: IPublicTypeNodeSchema): schema is IPublicTypeRootSchema {
if (!isSchema(schema)) {
return false;
}
return ['Page', 'Block', 'Component'].includes(schema.componentName);
}
/**
* check if current page is nested within another page with same host
* @returns boolean
*/
export function inSameDomain() {
try {
return window.parent !== window && window.parent.location.host === window.location.host;
} catch (e) {
return false;
}
}
/**
* get css styled name from schema`s fileName
* FileName -> lce-file-name
* @returns string
*/
export function getFileCssName(fileName: string) {
if (!fileName) {
return;
}
const name = fileName.replace(/([A-Z])/g, '-$1').toLowerCase();
return (`lce-${name}`)
.split('-')
.filter((p) => !!p)
.join('-');
}
/**
* check if a object is type of JSSlot
* @returns string
*/
export function isJSSlot(obj: any): obj is IPublicTypeJSSlot {
if (!obj) {
return false;
}
if (typeof obj !== 'object' || Array.isArray(obj)) {
return false;
}
// Compatible with the old protocol JSBlock
return [EXPRESSION_TYPE.JSSLOT, EXPRESSION_TYPE.JSBLOCK].includes(obj.type);
}
/**
* get value from an object
* @returns string
*/
export function getValue(obj: any, path: string, defaultValue = {}) {
// array is not valid type, return default value
if (Array.isArray(obj)) {
return defaultValue;
}
if (isEmpty(obj) || typeof obj !== 'object') {
return defaultValue;
}
const res = path.split('.').reduce((pre, cur) => {
return pre && pre[cur];
}, obj);
if (res === undefined) {
return defaultValue;
}
return res;
}
/**
* 用于处理国际化字符串
* @param {*} key 语料标识
* @param {*} values 字符串模版变量
* @param {*} locale 国际化标识,例如 zh-CN、en-US
* @param {*} messages 国际化语言包
*/
export function getI18n(key: string, values = {}, locale = 'zh-CN', messages: Record<string, any> = {}) {
if (!messages || !messages[locale] || !messages[locale][key]) {
return '';
}
const formater = new IntlMessageFormat(messages[locale][key], locale);
return formater.format(values);
}
/**
* 判断当前组件是否能够设置ref
* @param {*} Comp 需要判断的组件
*/
export function canAcceptsRef(Comp: any) {
const hasSymbol = typeof Symbol === 'function' && Symbol.for;
const REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0;
// eslint-disable-next-line max-len
return Comp?.$$typeof === REACT_FORWARD_REF_TYPE || Comp?.prototype?.isReactComponent || Comp?.prototype?.setState || Comp._forwardRef;
}
/**
* transform array to a object
* @param arr array to be transformed
* @param key key of array item, which`s value will be used as key in result map
* @param overwrite overwrite existing item in result or not
* @returns object result map
*/
export function transformArrayToMap(arr: any[], key: string, overwrite = true) {
if (isEmpty(arr) || !Array.isArray(arr)) {
return {};
}
const res: any = {};
arr.forEach((item) => {
const curKey = item[key];
if (item[key] === undefined) {
return;
}
if (res[curKey] && !overwrite) {
return;
}
res[curKey] = item;
});
return res;
}
/**
* transform string to a function
* @param str function in string form
* @returns funtion
*/
export function transformStringToFunction(str: string) {
if (typeof str !== 'string') {
return str;
}
if (inSameDomain() && (window.parent as any).__newFunc) {
return (window.parent as any).__newFunc(`"use strict"; return ${str}`)();
} else {
return new Function(`"use strict"; return ${str}`)();
}
}
/**
* 对象类型JSExpression支持省略this
* @param str expression in string form
* @param self scope object
* @returns funtion
*/
function parseExpression(options: {
str: any; self: any; thisRequired?: boolean; logScope?: string;
}): any;
function parseExpression(str: any, self: any, thisRequired?: boolean): any;
function parseExpression(a: any, b?: any, c = false) {
let str;
let self;
let thisRequired;
let logScope;
if (typeof a === 'object' && b === undefined) {
str = a.str;
self = a.self;
thisRequired = a.thisRequired;
logScope = a.logScope;
} else {
str = a;
self = b;
thisRequired = c;
}
try {
const contextArr = ['"use strict";', 'var __self = arguments[0];'];
contextArr.push('return ');
let tarStr: string;
tarStr = (str.value || '').trim();
// NOTE: use __self replace 'this' in the original function str
// may be wrong in extreme case which contains '__self' already
tarStr = tarStr.replace(/this(\W|$)/g, (_a: any, b: any) => `__self${b}`);
tarStr = contextArr.join('\n') + tarStr;
// 默认调用顶层窗口的parseObj, 保障new Function的window对象是顶层的window对象
if (inSameDomain() && (window.parent as any).__newFunc) {
return (window.parent as any).__newFunc(tarStr)(self);
}
const code = `with(${thisRequired ? '{}' : '$scope || {}'}) { ${tarStr} }`;
return new Function('$scope', code)(self);
} catch (err) {
logger.error(`${logScope || ''} parseExpression.error`, err, str, self?.__self ?? self);
return undefined;
}
}
export {
parseExpression,
};
export function parseThisRequiredExpression(str: any, self: any) {
return parseExpression(str, self, true);
}
/**
* capitalize first letter
* @param word string to be proccessed
* @returns string capitalized string
*/
export function capitalizeFirstLetter(word: string) {
if (!word || !isString(word) || word.length === 0) {
return word;
}
return word[0].toUpperCase() + word.slice(1);
}
/**
* check str passed in is a string type of not
* @param str obj to be checked
* @returns boolean
*/
export function isString(str: any): boolean {
return {}.toString.call(str) === '[object String]';
}
/**
* check if obj is type of variable structure
* @param obj object to be checked
* @returns boolean
*/
export function isVariable(obj: any) {
if (!obj || Array.isArray(obj)) {
return false;
}
return typeof obj === 'object' && obj?.type === 'variable';
}
/**
* 将 i18n 结构,降级解释为对 i18n 接口的调用
* @param i18nInfo object
* @param self context
*/
export function parseI18n(i18nInfo: any, self: any) {
return parseExpression({
type: EXPRESSION_TYPE.JSEXPRESSION,
value: `this.i18n('${i18nInfo.key}')`,
}, self);
}
/**
* for each key in targetObj, run fn with the value of the value, and the context paased in.
* @param targetObj object that keys will be for each
* @param fn function that process each item
* @param context
*/
export function forEach(targetObj: any, fn: any, context?: any) {
if (!targetObj || Array.isArray(targetObj) || isString(targetObj) || typeof targetObj !== 'object') {
return;
}
Object.keys(targetObj).forEach((key) => fn.call(context, targetObj[key], key));
}
interface IParseOptions {
thisRequiredInJSE?: boolean;
logScope?: string;
}
export function parseData(schema: unknown, self: any, options: IParseOptions = {}): any {
if (isJSExpression(schema)) {
return parseExpression({
str: schema,
self,
thisRequired: options.thisRequiredInJSE,
logScope: options.logScope,
});
} else if (isI18nData(schema)) {
return parseI18n(schema, self);
} else if (typeof schema === 'string') {
return schema.trim();
} else if (Array.isArray(schema)) {
return schema.map((item) => parseData(item, self, options));
} else if (typeof schema === 'function') {
return schema.bind(self);
} else if (typeof schema === 'object') {
// 对于undefined及null直接返回
if (!schema) {
return schema;
}
const res: any = {};
forEach(schema, (val: any, key: string) => {
if (key.startsWith('__')) {
return;
}
res[key] = parseData(val, self, options);
});
return res;
}
return schema;
}
/**
* process params for using in a url query
* @param obj params to be processed
* @returns string
*/
export function serializeParams(obj: any) {
let result: any = [];
forEach(obj, (val: any, key: any) => {
if (val === null || val === undefined || val === '') {
return;
}
if (typeof val === 'object') {
result.push(`${key}=${encodeURIComponent(JSON.stringify(val))}`);
} else {
result.push(`${key}=${encodeURIComponent(val)}`);
}
});
return result.join('&');
}