-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcommon.ts
More file actions
400 lines (368 loc) · 9.98 KB
/
common.ts
File metadata and controls
400 lines (368 loc) · 9.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
/* eslint-disable no-console */
/* eslint-disable no-new-func */
import logger from './logger';
import {
IPublicTypeRootSchema,
IPublicTypeNodeSchema,
IPublicTypeJSSlot,
} from '@felce/lowcode-types';
import { isI18nData, isJSExpression } from '@felce/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('&');
}