mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-12 03:01:16 +00:00
refactor: abstract excuteLifeCycleMethod and some minor refactors
This commit is contained in:
parent
6399cce05a
commit
706dad4aa5
@ -10,7 +10,7 @@ const jestConfig = {
|
|||||||
// // '^.+\\.(js|jsx)$': 'babel-jest',
|
// // '^.+\\.(js|jsx)$': 'babel-jest',
|
||||||
// },
|
// },
|
||||||
// testMatch: ['(/tests?/.*(test))\\.[jt]s$'],
|
// testMatch: ['(/tests?/.*(test))\\.[jt]s$'],
|
||||||
// testMatch: ['**/*/common.test.ts'],
|
// testMatch: ['**/*/base.test.tsx'],
|
||||||
transformIgnorePatterns: [
|
transformIgnorePatterns: [
|
||||||
`/node_modules/(?!${esModules})/`,
|
`/node_modules/(?!${esModules})/`,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -35,6 +35,38 @@ import { IComponentConstruct, IComponentHoc, leafWrapper } from '../hoc/leaf';
|
|||||||
import logger from '../utils/logger';
|
import logger from '../utils/logger';
|
||||||
import isUseLoop from '../utils/is-use-loop';
|
import isUseLoop from '../utils/is-use-loop';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* execute method in schema.lifeCycles with context
|
||||||
|
* @PRIVATE
|
||||||
|
*/
|
||||||
|
export function excuteLifeCycleMethod(context: any, schema: NodeSchema, method: string, args: any, thisRequiredInJSE: boolean | undefined): any {
|
||||||
|
if (!context || !isSchema(schema) || !method) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lifeCycleMethods = getValue(schema, 'lifeCycles', {});
|
||||||
|
let fn = lifeCycleMethods[method];
|
||||||
|
|
||||||
|
if (!fn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: cache
|
||||||
|
if (isJSExpression(fn) || isJSFunction(fn)) {
|
||||||
|
fn = thisRequiredInJSE ? parseThisRequiredExpression(fn, context) : parseExpression(fn, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof fn !== 'function') {
|
||||||
|
console.error(`生命周期${method}类型不符`, fn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return fn.apply(context, args);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[${schema.componentName}]生命周期${method}出错`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default function baseRendererFactory(): IBaseRenderComponent {
|
export default function baseRendererFactory(): IBaseRenderComponent {
|
||||||
const { BaseRenderer: customBaseRenderer } = adapter.getRenderers();
|
const { BaseRenderer: customBaseRenderer } = adapter.getRenderers();
|
||||||
|
|
||||||
@ -72,32 +104,33 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
|
|
||||||
static contextType = AppContext;
|
static contextType = AppContext;
|
||||||
|
|
||||||
__namespace = 'base';
|
|
||||||
|
|
||||||
appHelper?: IRendererAppHelper;
|
appHelper?: IRendererAppHelper;
|
||||||
|
i18n: any;
|
||||||
|
getLocale: any;
|
||||||
|
setLocale: any;
|
||||||
|
dataSourceMap: Record<string, any> = {};
|
||||||
|
|
||||||
|
__namespace = 'base';
|
||||||
__compScopes: Record<string, any> = {};
|
__compScopes: Record<string, any> = {};
|
||||||
__instanceMap: Record<string, any> = {};
|
__instanceMap: Record<string, any> = {};
|
||||||
__dataHelper: any;
|
__dataHelper: any;
|
||||||
__customMethodsList: any[] = [];
|
__customMethodsList: any[] = [];
|
||||||
dataSourceMap: Record<string, any> = {};
|
__parseExpression: any;
|
||||||
__ref: any;
|
__ref: any;
|
||||||
i18n: any;
|
|
||||||
getLocale: any;
|
|
||||||
setLocale: any;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* reference of style element contains schema.css
|
* reference of style element contains schema.css
|
||||||
*
|
*
|
||||||
* @type {any}
|
* @type {any}
|
||||||
*/
|
*/
|
||||||
styleElement: any;
|
__styleElement: any;
|
||||||
parseExpression: any;
|
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
|
|
||||||
constructor(props: IBaseRendererProps, context: IBaseRendererContext) {
|
constructor(props: IBaseRendererProps, context: IBaseRendererContext) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.parseExpression = props?.thisRequiredInJSE ? parseThisRequiredExpression : parseExpression;
|
this.__parseExpression = props?.thisRequiredInJSE ? parseThisRequiredExpression : parseExpression;
|
||||||
this.__beforeInit(props);
|
this.__beforeInit(props);
|
||||||
this.__init(props);
|
this.__init(props);
|
||||||
this.__afterInit(props);
|
this.__afterInit(props);
|
||||||
@ -119,21 +152,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
__afterInit(_props: IBaseRendererProps) { }
|
__afterInit(_props: IBaseRendererProps) { }
|
||||||
|
|
||||||
static getDerivedStateFromProps(props: IBaseRendererProps, state: any) {
|
static getDerivedStateFromProps(props: IBaseRendererProps, state: any) {
|
||||||
logger.log('getDerivedStateFromProps');
|
return excuteLifeCycleMethod(this, props?.__schema, 'getDerivedStateFromProps', [props, state], props.thisRequiredInJSE);
|
||||||
const func = props?.__schema?.lifeCycles?.getDerivedStateFromProps;
|
|
||||||
|
|
||||||
if (func) {
|
|
||||||
if (isJSExpression(func) || isJSFunction(func)) {
|
|
||||||
const fn = props.thisRequiredInJSE ? parseThisRequiredExpression(func, this) : parseExpression(func, this);
|
|
||||||
return fn?.(props, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof func === 'function') {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
||||||
return (func as Function)(props, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSnapshotBeforeUpdate(...args: any[]) {
|
async getSnapshotBeforeUpdate(...args: any[]) {
|
||||||
@ -198,23 +217,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
* @PRIVATE
|
* @PRIVATE
|
||||||
*/
|
*/
|
||||||
__excuteLifeCycleMethod = (method: string, args?: any) => {
|
__excuteLifeCycleMethod = (method: string, args?: any) => {
|
||||||
const lifeCycleMethods = getValue(this.props.__schema, 'lifeCycles', {});
|
excuteLifeCycleMethod(this, this.props.__schema, method, args, this.props.thisRequiredInJSE);
|
||||||
let fn = lifeCycleMethods[method];
|
|
||||||
if (fn) {
|
|
||||||
// TODO: cache
|
|
||||||
if (isJSExpression(fn) || isJSFunction(fn)) {
|
|
||||||
fn = this.parseExpression(fn, this);
|
|
||||||
}
|
|
||||||
if (typeof fn !== 'function') {
|
|
||||||
console.error(`生命周期${method}类型不符`, fn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return fn.apply(this, args);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`[${this.props.__schema.componentName}]生命周期${method}出错`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -242,7 +245,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
forEach(__schema.methods, (val: any, key: string) => {
|
forEach(__schema.methods, (val: any, key: string) => {
|
||||||
let value = val;
|
let value = val;
|
||||||
if (isJSExpression(value) || isJSFunction(value)) {
|
if (isJSExpression(value) || isJSFunction(value)) {
|
||||||
value = this.parseExpression(value, this);
|
value = this.__parseExpression(value, this);
|
||||||
}
|
}
|
||||||
if (typeof value !== 'function') {
|
if (typeof value !== 'function') {
|
||||||
console.error(`自定义函数${key}类型不符`, value);
|
console.error(`自定义函数${key}类型不符`, value);
|
||||||
@ -351,16 +354,16 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
__writeCss = (props: IBaseRendererProps) => {
|
__writeCss = (props: IBaseRendererProps) => {
|
||||||
const css = getValue(props.__schema, 'css', '');
|
const css = getValue(props.__schema, 'css', '');
|
||||||
this.__debug('create this.styleElement with css', css);
|
this.__debug('create this.styleElement with css', css);
|
||||||
let style = this.styleElement;
|
let style = this.__styleElement;
|
||||||
if (!this.styleElement) {
|
if (!this.__styleElement) {
|
||||||
style = document.createElement('style');
|
style = document.createElement('style');
|
||||||
style.type = 'text/css';
|
style.type = 'text/css';
|
||||||
style.setAttribute('from', 'style-sheet');
|
style.setAttribute('from', 'style-sheet');
|
||||||
|
|
||||||
const head = document.head || document.getElementsByTagName('head')[0];
|
const head = document.head || document.getElementsByTagName('head')[0];
|
||||||
head.appendChild(style);
|
head.appendChild(style);
|
||||||
this.styleElement = style;
|
this.__styleElement = style;
|
||||||
this.__debug('this.styleElement is created', this.styleElement);
|
this.__debug('this.styleElement is created', this.__styleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (style.innerHTML === css) {
|
if (style.innerHTML === css) {
|
||||||
@ -459,7 +462,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
const { __appHelper: appHelper, __components: components = {} } = this.props || {};
|
const { __appHelper: appHelper, __components: components = {} } = this.props || {};
|
||||||
|
|
||||||
if (isJSExpression(schema)) {
|
if (isJSExpression(schema)) {
|
||||||
return this.parseExpression(schema, scope);
|
return this.__parseExpression(schema, scope);
|
||||||
}
|
}
|
||||||
if (isI18nData(schema)) {
|
if (isI18nData(schema)) {
|
||||||
return parseI18n(schema, scope);
|
return parseI18n(schema, scope);
|
||||||
@ -486,7 +489,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
const _children = this.__getSchemaChildren(schema);
|
const _children = this.__getSchemaChildren(schema);
|
||||||
// 解析占位组件
|
// 解析占位组件
|
||||||
if (schema.componentName === 'Fragment' && _children) {
|
if (schema.componentName === 'Fragment' && _children) {
|
||||||
const tarChildren = isJSExpression(_children) ? this.parseExpression(_children, scope) : _children;
|
const tarChildren = isJSExpression(_children) ? this.__parseExpression(_children, scope) : _children;
|
||||||
return this.__createVirtualDom(tarChildren, scope, parentInfo);
|
return this.__createVirtualDom(tarChildren, scope, parentInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,7 +556,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
let scopeKey = '';
|
let scopeKey = '';
|
||||||
// 判断组件是否需要生成scope,且只生成一次,挂在this.__compScopes上
|
// 判断组件是否需要生成scope,且只生成一次,挂在this.__compScopes上
|
||||||
if (Comp.generateScope) {
|
if (Comp.generateScope) {
|
||||||
const key = this.parseExpression(schema.props?.key, scope);
|
const key = this.__parseExpression(schema.props?.key, scope);
|
||||||
if (key) {
|
if (key) {
|
||||||
// 如果组件自己设置key则使用组件自己的key
|
// 如果组件自己设置key则使用组件自己的key
|
||||||
scopeKey = key;
|
scopeKey = key;
|
||||||
@ -704,7 +707,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
|
|
||||||
children.forEach((child: any) => {
|
children.forEach((child: any) => {
|
||||||
const childVirtualDom = this.__createVirtualDom(
|
const childVirtualDom = this.__createVirtualDom(
|
||||||
isJSExpression(child) ? this.parseExpression(child, scope) : child,
|
isJSExpression(child) ? this.__parseExpression(child, scope) : child,
|
||||||
scope,
|
scope,
|
||||||
{
|
{
|
||||||
schema,
|
schema,
|
||||||
@ -806,7 +809,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isJSExpression(props)) {
|
if (isJSExpression(props)) {
|
||||||
props = this.parseExpression(props, scope);
|
props = this.__parseExpression(props, scope);
|
||||||
// 只有当变量解析出来为模型结构的时候才会继续解析
|
// 只有当变量解析出来为模型结构的时候才会继续解析
|
||||||
if (!isSchema(props) && !isJSSlot(props)) {
|
if (!isSchema(props) && !isJSSlot(props)) {
|
||||||
return checkProps(props);
|
return checkProps(props);
|
||||||
|
|||||||
@ -40,8 +40,6 @@ export default function pageRendererFactory(): IBaseRenderComponent {
|
|||||||
this.__bindCustomMethods(this.props);
|
this.__bindCustomMethods(this.props);
|
||||||
this.__initDataSource(this.props);
|
this.__initDataSource(this.props);
|
||||||
|
|
||||||
// this.__excuteLifeCycleMethod('constructor', arguments);
|
|
||||||
|
|
||||||
this.__generateCtx({
|
this.__generateCtx({
|
||||||
page: this,
|
page: this,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,13 +1,31 @@
|
|||||||
|
|
||||||
|
import React, { Component, createElement, PureComponent, createContext } from 'react';
|
||||||
const mockGetRenderers = jest.fn();
|
const mockGetRenderers = jest.fn();
|
||||||
|
const mockGetRuntime = jest.fn();
|
||||||
|
const mockParseExpression = jest.fn();
|
||||||
jest.mock('../../src/adapter', () => {
|
jest.mock('../../src/adapter', () => {
|
||||||
return {
|
return {
|
||||||
getRenderers: () => { return mockGetRenderers();}
|
getRenderers: () => { return mockGetRenderers();},
|
||||||
|
getRuntime: () => { return mockGetRuntime();},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
jest.mock('../../src/utils', () => {
|
||||||
|
const originalUtils = jest.requireActual('../../src/utils');
|
||||||
|
return {
|
||||||
|
...originalUtils,
|
||||||
|
parseExpression: (...args) => { mockParseExpression(args);},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
import baseRendererFactory from '../../src/renderer/base';
|
|
||||||
|
|
||||||
describe('Base Render', () => {
|
import baseRendererFactory from '../../src/renderer/base';
|
||||||
|
import { IBaseRendererProps } from '../../src/types';
|
||||||
|
import TestRenderer from 'react-test-renderer';
|
||||||
|
import components from '../utils/components';
|
||||||
|
import schema from '../fixtures/schema/basic';
|
||||||
|
|
||||||
|
|
||||||
|
describe('Base Render factory', () => {
|
||||||
it('customBaseRenderer logic works', () => {
|
it('customBaseRenderer logic works', () => {
|
||||||
mockGetRenderers.mockReturnValue({BaseRenderer: {}});
|
mockGetRenderers.mockReturnValue({BaseRenderer: {}});
|
||||||
const baseRenderer = baseRendererFactory();
|
const baseRenderer = baseRendererFactory();
|
||||||
@ -15,4 +33,190 @@ describe('Base Render', () => {
|
|||||||
expect(baseRenderer).toStrictEqual({});
|
expect(baseRenderer).toStrictEqual({});
|
||||||
mockGetRenderers.mockClear();
|
mockGetRenderers.mockClear();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Base Render methods', () => {
|
||||||
|
let RendererClass;
|
||||||
|
const mockRendererFactory = () => {
|
||||||
|
return class extends Component {
|
||||||
|
constructor(props: IBaseRendererProps, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
beforeEach(() => {
|
||||||
|
const mockRnederers = {
|
||||||
|
PageRenderer: mockRendererFactory(),
|
||||||
|
ComponentRenderer: mockRendererFactory(),
|
||||||
|
BlockRenderer: mockRendererFactory(),
|
||||||
|
AddonRenderer: mockRendererFactory(),
|
||||||
|
TempRenderer: mockRendererFactory(),
|
||||||
|
DivRenderer: mockRendererFactory(),
|
||||||
|
};
|
||||||
|
mockGetRenderers.mockReturnValue(mockRnederers);
|
||||||
|
mockGetRuntime.mockReturnValue({
|
||||||
|
Component,
|
||||||
|
createElement,
|
||||||
|
PureComponent,
|
||||||
|
createContext,
|
||||||
|
});
|
||||||
|
RendererClass = baseRendererFactory();
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockGetRenderers.mockClear();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should excute lifecycle.getDerivedStateFromProps when defined', () => {
|
||||||
|
const mockGetDerivedStateFromProps = {
|
||||||
|
type: 'JSFunction',
|
||||||
|
value: 'function() {\n console.log(\'did mount\');\n }',
|
||||||
|
};
|
||||||
|
const mockSchema = schema;
|
||||||
|
(mockSchema.lifeCycles as any).getDerivedStateFromProps = mockGetDerivedStateFromProps;
|
||||||
|
|
||||||
|
// const originalUtils = jest.requireActual('../../src/utils');
|
||||||
|
// mockParseExpression.mockImplementation(originalUtils.parseExpression);
|
||||||
|
const component = TestRenderer.create(
|
||||||
|
// @ts-ignore
|
||||||
|
<RendererClass
|
||||||
|
__schema={mockSchema}
|
||||||
|
components={components as any}
|
||||||
|
thisRequiredInJSE={false}
|
||||||
|
a='1'
|
||||||
|
/>);
|
||||||
|
// console.log(component.root.props.a);
|
||||||
|
// component.update(<RendererClasssnippets
|
||||||
|
// schema={mockSchema}
|
||||||
|
// components={components as any}
|
||||||
|
// thisRequiredInJSE={false}
|
||||||
|
// a='2'
|
||||||
|
// />);
|
||||||
|
// console.log(component.root.props.a);
|
||||||
|
// expect(mockParseExpression).toHaveBeenCalledWith(mockGetDerivedStateFromProps, expect.anything())
|
||||||
|
// test lifecycle.getDerivedStateFromProps is null
|
||||||
|
|
||||||
|
// test lifecycle.getDerivedStateFromProps is JSExpression
|
||||||
|
|
||||||
|
// test lifecycle.getDerivedStateFromProps is JSFunction
|
||||||
|
|
||||||
|
// test lifecycle.getDerivedStateFromProps is function
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// it('should excute lifecycle.getSnapshotBeforeUpdate when defined', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('should excute lifecycle.componentDidMount when defined', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('should excute lifecycle.componentDidUpdate when defined', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('should excute lifecycle.componentWillUnmount when defined', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('should excute lifecycle.componentDidCatch when defined', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__excuteLifeCycleMethod should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('reloadDataSource should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('shouldComponentUpdate should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
// it('_getComponentView should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__bindCustomMethods should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__generateCtx should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__parseData should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__initDataSource should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__initI18nAPIs should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__writeCss should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__render should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__getSchemaChildren should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__createDom should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__createVirtualDom should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__componentHoc should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__getSchemaChildrenVirtualDom should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__getComponentProps should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__createLoopVirtualDom should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__designModeIsDesign should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__parseProps should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('$ should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__renderContextProvider should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__renderContextConsumer should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__getHocComp should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__renderComp should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__renderContent should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('__checkSchema should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('requestHandlersMap should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('utils should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('constants should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('history should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('location should work', () => {
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('match should work', () => {
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user